import * as z from "zod" import { tool } from "langchain" import os from 'os' import { promisify } from 'util' import { exec } from 'child_process' const execPromise = promisify(exec) /** * 获取系统文件信息工具 * @returns {string} - 返回系统文件信息的 JSON 字符串,包括分区信息和各分区剩余空间 */ export const getSystemFileInfoTool = tool( async (input) => { try { const fileSystemInfo = { platform: os.platform(), partitions: [], volumeInfo: [], diskHealth: [], inodeInfo: [] } // 获取文件系统信息(Windows) if (os.platform() === 'win32') { try { const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object DeviceID,Description,FileSystem,Size,FreeSpace,VolumeName | ConvertTo-Json"') const partitionData = JSON.parse(stdout) const partitionsArray = Array.isArray(partitionData) ? partitionData : [partitionData] fileSystemInfo.partitions = partitionsArray.map(disk => { const size = disk.Size ? parseInt(disk.Size) : 0 const freeSpace = disk.FreeSpace ? parseInt(disk.FreeSpace) : 0 const usedSpace = size - freeSpace return { device: disk.DeviceID || 'N/A', description: disk.Description || 'N/A', filesystem: disk.FileSystem || 'N/A', volumeName: disk.VolumeName || 'N/A', size: size > 0 ? `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A', freeSpace: freeSpace > 0 ? `${(freeSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A', usedSpace: usedSpace > 0 ? `${(usedSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A', usePercent: size > 0 ? `${((usedSpace / size) * 100).toFixed(2)}%` : 'N/A' } }).filter(item => item) // 获取卷详细信息(Windows) try { const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object VolumeName,VolumeSerialNumber,FileSystem,MaximumComponentLength,DeviceID,DriveType | ConvertTo-Json"') const allVolumeData = JSON.parse(stdout) const volumeArray = Array.isArray(allVolumeData) ? allVolumeData : [allVolumeData] fileSystemInfo.volumeInfo = volumeArray.map(vol => ({ device: vol.DeviceID || 'N/A', volumeName: vol.VolumeName || 'N/A', serialNumber: vol.VolumeSerialNumber || 'N/A', fileSystem: vol.FileSystem || 'N/A', maxComponentLength: vol.MaximumComponentLength || 'N/A', deviceID: vol.DeviceID || 'N/A', driveType: vol.DriveType === 2 ? 'Removable' : vol.DriveType === 3 ? 'Local' : vol.DriveType === 4 ? 'Network' : vol.DriveType === 5 ? 'CD-ROM' : 'Unknown' })).filter(item => item) } catch (error) { console.error('Error getting volume info:', error) } // 获取磁盘健康状态(Windows SMART) try { const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_DiskDrive | Select-Object Model,Status,SerialNumber,Size,MediaType | ConvertTo-Json"') const diskData = JSON.parse(stdout) const diskArray = Array.isArray(diskData) ? diskData : [diskData] fileSystemInfo.diskHealth = diskArray.map(disk => { return { model: disk.Model || 'N/A', status: disk.Status || 'N/A', serialNumber: disk.SerialNumber || 'N/A', size: disk.Size ? `${(parseInt(disk.Size) / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A' } }).filter(item => item) } catch (error) { console.error('Error getting disk health:', error) } } catch (error) { console.error('Error getting file system info:', error) fileSystemInfo.error = 'Error getting file system info' } } // 获取文件系统信息(Linux) else if (os.platform() === 'linux') { try { const { stdout } = await execPromise('df -h') fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => { const parts = line.trim().split(/\s+/) if (parts.length >= 6) { return { device: parts[0], size: parts[1], usedSpace: parts[2], freeSpace: parts[3], usePercent: parts[4], mountedOn: parts[5] } } return null }).filter(item => item) // 获取挂载选项和文件系统类型(Linux) try { const { stdout } = await execPromise('mount | column -t') const mountLines = stdout.trim().split('\n') const mountInfo = {} mountLines.forEach(line => { const match = line.match(/^(.+?)\s+on\s+(.+?)\s+type\s+(.+?)\s+\((.*)\)$/) if (match) { mountInfo[match[2]] = { device: match[1], filesystem: match[3], options: match[4] } } }) fileSystemInfo.mountInfo = mountInfo } catch (error) { console.error('Error getting mount info:', error) } // 获取inode信息(Linux) try { const { stdout } = await execPromise('df -i') fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => { const parts = line.trim().split(/\s+/) if (parts.length >= 6) { return { device: parts[0], totalInodes: parts[1], usedInodes: parts[2], freeInodes: parts[3], usePercent: parts[4], mountedOn: parts[5] } } return null }).filter(item => item) } catch (error) { console.error('Error getting inode info:', error) } // 获取磁盘健康状态(Linux SMART) try { const { stdout } = await execPromise('smartctl --health --json /dev/sda 2>/dev/null || echo "{}"') if (stdout && stdout.includes('smart_status')) { const healthData = JSON.parse(stdout) fileSystemInfo.diskHealth.push({ device: '/dev/sda', status: healthData.smart_status?.passed ? 'OK' : 'Failed', powerOnHours: healthData.power_on_time?.hours || 'N/A', temperature: healthData.temperature?.current || 'N/A' }) } } catch (error) { console.log('SMART info not available or requires root') } } catch (error) { console.error('Error getting file system info:', error) fileSystemInfo.error = 'Error getting file system info' } } // 获取文件系统信息(macOS) else if (os.platform() === 'darwin') { try { const { stdout } = await execPromise('df -h') fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => { const parts = line.trim().split(/\s+/) if (parts.length >= 6) { return { device: parts[0], size: parts[1], usedSpace: parts[2], freeSpace: parts[3], usePercent: parts[4], mountedOn: parts[5] } } return null }).filter(item => item) // 获取卷详细信息(macOS) try { const diskInfo = await execPromise('diskutil info -all') const diskLines = diskInfo.stdout.trim().split('\n\n') diskLines.forEach(disk => { const volMatch = disk.match(/Volume Name:\s+(.+)/) const devMatch = disk.match(/Device Node:\s+(.+)/) const fsMatch = disk.match(/File System Personality:\s+(.+)/) const serialMatch = disk.match(/Disk UUID:\s+(.+)/) if (devMatch) { fileSystemInfo.volumeInfo.push({ device: devMatch[1], volumeName: volMatch ? volMatch[1] : 'N/A', filesystem: fsMatch ? fsMatch[1] : 'N/A', serialNumber: serialMatch ? serialMatch[1] : 'N/A' }) } }) } catch (error) { console.error('Error getting volume info:', error) } // 获取inode信息(macOS) try { const { stdout } = await execPromise('df -i') fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => { const parts = line.trim().split(/\s+/) if (parts.length >= 6) { return { device: parts[0], totalInodes: parts[1], usedInodes: parts[2], freeInodes: parts[3], usePercent: parts[4], mountedOn: parts[5] } } return null }).filter(item => item) } catch (error) { console.error('Error getting inode info:', error) } // 获取磁盘健康状态(macOS) try { const { stdout } = await execPromise('diskutil smartStatus -all 2>/dev/null || echo "N/A"') if (stdout && stdout !== 'N/A') { fileSystemInfo.diskHealth = stdout.trim() } } catch (error) { console.log('SMART status not available') } } catch (error) { console.error('Error getting file system info:', error) fileSystemInfo.error = 'Error getting file system info' } } return JSON.stringify(fileSystemInfo, null, 2) } catch (error) { console.error('Error getting file system info:', error) return JSON.stringify({ error: error.message }, null, 2) } }, { name: "get_system_file_info", description: `获取系统硬盘分区和文件系统信息,包括分区信息、卷标、序列号、磁盘健康状态、inode信息等`, schema: z.object({}) } )