using System.Diagnostics; namespace AxCopilot.Services; /// /// 런타임 환경(Python, Node.js 등) 설치 여부를 감지하는 정적 헬퍼. /// 스킬 시스템에서 런타임 의존 스킬의 가용성을 판단하는 데 사용합니다. /// 결과는 5분간 캐시되어 반복 호출 시 프로세스 생성을 최소화합니다. /// public static class RuntimeDetector { private static Dictionary? _cache; private static DateTime _cacheTime; private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5); /// 지정된 런타임이 설치되어 PATH에서 접근 가능한지 확인합니다. /// 런타임 이름 (python, node, dotnet 등) public static bool IsAvailable(string runtime) { if (string.IsNullOrWhiteSpace(runtime)) return true; runtime = runtime.Trim().ToLowerInvariant(); // 캐시 확인 if (_cache != null && (DateTime.UtcNow - _cacheTime) < CacheTtl) { if (_cache.TryGetValue(runtime, out var cached)) return cached; } else { _cache = new Dictionary(StringComparer.OrdinalIgnoreCase); _cacheTime = DateTime.UtcNow; } var available = DetectRuntime(runtime); _cache[runtime] = available; return available; } /// 캐시를 강제로 초기화합니다. public static void ClearCache() => _cache = null; private static bool DetectRuntime(string runtime) { // 1단계: where.exe로 PATH 존재 확인 var wherePath = RunQuick("where.exe", runtime); if (string.IsNullOrEmpty(wherePath)) return false; // 2단계: --version으로 실행 가능 확인 var versionArg = runtime switch { "java" => "-version", _ => "--version", }; var version = RunQuick(runtime, versionArg); return !string.IsNullOrEmpty(version); } private static string? RunQuick(string exe, string args) { try { var psi = new ProcessStartInfo(exe, args) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; using var proc = Process.Start(psi); if (proc == null) return null; var output = proc.StandardOutput.ReadToEnd(); var error = proc.StandardError.ReadToEnd(); proc.WaitForExit(5000); var result = string.IsNullOrWhiteSpace(output) ? error : output; return string.IsNullOrWhiteSpace(result) ? null : result.Trim(); } catch { return null; } } }