@@ -24,6 +24,22 @@ export interface LocalBackendConfig {
2424 vllmCommand ?: string ;
2525}
2626
27+ export interface LocalRuntimeStatus {
28+ backend : LocalBackendId ;
29+ compatible : boolean ;
30+ installed : boolean ;
31+ running : boolean ;
32+ model ?: string ;
33+ detail : string ;
34+ }
35+
36+ export interface RuntimeProbe {
37+ compatible : boolean ;
38+ command : string ;
39+ args : string [ ] ;
40+ detail : string ;
41+ }
42+
2743interface RunningServer {
2844 process : ChildProcess ;
2945 model : string ;
@@ -47,6 +63,92 @@ const IDLE_TIMEOUT_MS = Number.isFinite(configuredIdleMinutes)
4763const servers = new Map < LocalBackendId , RunningServer > ( ) ;
4864const serverStarts = new Map < LocalBackendId , { model : string ; promise : Promise < string > } > ( ) ;
4965
66+ export function buildRuntimeProbe (
67+ backend : LocalBackendId ,
68+ config : LocalBackendConfig ,
69+ platform : NodeJS . Platform = process . platform ,
70+ arch : string = process . arch
71+ ) : RuntimeProbe {
72+ if ( backend === "mlx" ) {
73+ const compatible = platform === "darwin" && arch === "arm64" ;
74+ return {
75+ compatible,
76+ command : config . mlxPythonPath ?. trim ( ) || "python3" ,
77+ args : [ "-c" , "import mlx_lm" ] ,
78+ detail : compatible ? "Apple Silicon accelerated runtime" : "Requires an Apple Silicon Mac" ,
79+ } ;
80+ }
81+ if ( backend === "vllm" ) {
82+ const compatible = platform === "linux" || platform === "win32" ;
83+ if ( ! config . vllmCommand ?. trim ( ) && platform === "win32" ) {
84+ return {
85+ compatible,
86+ command : "wsl.exe" ,
87+ args : [ "--" , "vllm" , "--version" ] ,
88+ detail : "CUDA or ROCm runtime through WSL" ,
89+ } ;
90+ }
91+ return {
92+ compatible,
93+ command : config . vllmCommand ?. trim ( ) || "vllm" ,
94+ args : [ "--version" ] ,
95+ detail : compatible ? "High-throughput CUDA or ROCm runtime" : "Requires Linux or Windows with WSL" ,
96+ } ;
97+ }
98+ const compatible = platform === "linux" || ! ! config . rocmServerPath ?. trim ( ) ;
99+ return {
100+ compatible,
101+ command : config . rocmServerPath ?. trim ( ) || "llama-server" ,
102+ args : [ "--version" ] ,
103+ detail : compatible ? "AMD GPU runtime for local GGUF models" : "Requires Linux and a ROCm-capable AMD GPU" ,
104+ } ;
105+ }
106+
107+ async function commandSucceeds ( command : string , args : string [ ] ) : Promise < boolean > {
108+ return new Promise ( ( resolve ) => {
109+ let settled = false ;
110+ let timer : NodeJS . Timeout ;
111+ const finish = ( value : boolean ) => {
112+ if ( settled ) return ;
113+ settled = true ;
114+ clearTimeout ( timer ) ;
115+ resolve ( value ) ;
116+ } ;
117+ let child : ChildProcess ;
118+ try {
119+ child = spawn ( command , args , { stdio : "ignore" } ) ;
120+ } catch {
121+ resolve ( false ) ;
122+ return ;
123+ }
124+ timer = setTimeout ( ( ) => {
125+ child . kill ( ) ;
126+ finish ( false ) ;
127+ } , 5_000 ) ;
128+ timer . unref ( ) ;
129+ child . once ( "error" , ( ) => finish ( false ) ) ;
130+ child . once ( "exit" , ( code ) => finish ( code === 0 ) ) ;
131+ } ) ;
132+ }
133+
134+ export async function getRuntimeStatuses ( config : LocalBackendConfig ) : Promise < LocalRuntimeStatus [ ] > {
135+ return Promise . all (
136+ ( [ "rocm" , "mlx" , "vllm" ] as const ) . map ( async ( backend ) => {
137+ const probe = buildRuntimeProbe ( backend , config ) ;
138+ const running = servers . get ( backend ) ;
139+ const installed = probe . compatible && ( running ? ! running . exited : await commandSucceeds ( probe . command , probe . args ) ) ;
140+ return {
141+ backend,
142+ compatible : probe . compatible ,
143+ installed,
144+ running : ! ! running && ! running . exited ,
145+ model : running && ! running . exited ? running . model : undefined ,
146+ detail : probe . detail ,
147+ } ;
148+ } )
149+ ) ;
150+ }
151+
50152function clearIdleTimer ( server : RunningServer ) : void {
51153 if ( ! server . idleTimer ) return ;
52154 clearTimeout ( server . idleTimer ) ;
0 commit comments