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; }
}
}