// Discovery engine: probes /v1/models + /props + LM Studio REST + /proc scan, // classifies server kind, and computes per-model metadata. All functions are // module-level (no shared closure) — easy to lazy-load. import * as http from "node:http"; import { readFileSync, readdirSync } from "node:fs"; import { baseName, isLocalHost, modelOptionsFor, serverLabel, shared } from "./core.ts"; import type { EndpointResult, HttpResult, LlamaCppModel, LlamaCppModelsResponse, LmStudioModelInfo, LmStudioModelsResponse, LocalServerInfo, ModelMetadata, ParsedServerArgs, ScanResult, ServerConfig, ServerKind, ServerProps, SettingsConfig, } from "./types.ts"; // ── GGUF quant tag extraction ────────────────────────────────────────────── const QUANT_TAG_RE = /[-._\s]?((?:UD-|KL1-)?(?:I?Q[1-8][._][0-9A-Z_]+|IQ[1-4]_[0-9A-Z]+|TQ[1-4]_[0-9]|F16|F32|BF16|FP16|FP32|MXFP4[0-9A-Z_]*))$/i; export function extractQuantTag(filenameOrId: string): string | undefined { const colon = filenameOrId.lastIndexOf(":"); if (colon > 0) { const suffix = filenameOrId.slice(colon + 1); if (/^(I?Q[1-8]_|TQ[1-4]_|F16|F32|BF16|FP16|MXFP4)/i.test(suffix)) return suffix.toUpperCase(); } const base = baseName(filenameOrId).replace(/\.(gguf|ggml)$/i, ""); const match = base.match(QUANT_TAG_RE); return match?.[1]?.toUpperCase(); } // ── llama-server flag parsing ────────────────────────────────────────────── export function parseServerArgs(tokens: string[]): ParsedServerArgs { const out: ParsedServerArgs = {}; let textOnly = false; const take = (i: number): string | undefined => { const v = tokens[i + 1]; return v && !v.startsWith("-") ? v : undefined; }; for (let i = 0; i < tokens.length; i++) { let tok = tokens[i]; if (!tok.startsWith("-")) continue; let inline: string | undefined; const eq = tok.indexOf("="); if (eq > 0) { inline = tok.slice(eq + 1); tok = tok.slice(0, eq); } const val = inline ?? take(i); const t = tok.toLowerCase(); if (t === "--cache-type-k" || t === "-ctk") out.cacheK = (val ?? "").toLowerCase() || undefined; else if (t === "--cache-type-v" || t === "-ctv") out.cacheV = (val ?? "").toLowerCase() || undefined; else if (t === "--model-draft" || t === "-md" || t === "--hf-repo-draft" || t === "-hfrd") { out.hasDraft = true; if (val) out.draft = baseName(val); } else if (t === "--mmproj") { out.hasMmproj = true; if (val) out.mmprojPath = val; } else if (t === "--language-model-only") textOnly = true; else if (t === "--limit-mm-per-prompt" || t === "--limit_mm_per_prompt") { const limit = imageLimitFromArg(val); if (limit !== undefined) out.acceptsImages = limit > 0; } } if (textOnly) out.acceptsImages = false; return out; } /** Parse vLLM's --limit-mm-per-prompt value: JSON map ({"image":2}) or the * legacy comma-separated key=value form (image=2). Returns the image limit, or * undefined when there is no image entry. */ function imageLimitFromArg(raw: string | undefined): number | undefined { if (!raw) return undefined; let limit: unknown; try { const parsed = JSON.parse(raw); if (parsed && typeof parsed === "object") limit = (parsed as Record).image; } catch { // legacy key=value form below } if (limit === undefined) { for (const part of raw.split(",")) { const [k, v] = part.split("=").map((s) => s.trim()); if (k === "image") { limit = v; break; } } } const n = typeof limit === "number" ? limit : parseInt(String(limit), 10); return Number.isFinite(n) ? n : undefined; } // ── Local /proc scan (loopback only) ─────────────────────────────────────── /** `--port N` / `--port=N`; undefined when absent or unparsable. */ function portFromArgs(args: string[]): number | undefined { const i = args.indexOf("--port"); if (i >= 0) { const port = parseInt(args[i + 1] ?? "", 10); if (!Number.isNaN(port)) return port; } for (const arg of args) { if (!arg.startsWith("--port=")) continue; const port = parseInt(arg.slice("--port=".length), 10); if (!Number.isNaN(port)) return port; } return undefined; } /** vLLM/ROCm engines run as `python -m vllm…` or `VLLM::EngineCore`; renamed * wrappers are caught by the flag surface in looksLikeOpenAiEngine(). */ function looksLikeVllmProcess(args: string[]): boolean { const joined = args.join("\0").toLowerCase(); return joined.includes("vllm"); } export function looksLikeOpenAiEngine(args: string[]): boolean { const joined = args.join("\0").toLowerCase(); if (joined.includes("vllm") || joined.includes("sglang")) return true; const has = (f: string) => args.some((a) => a === f || a.startsWith(f + "=")); if (!has("--served-model-name")) return false; return has("--max-model-len") || has("--tensor-parallel-size") || has("--gpu-memory-utilization") || has("--kv-cache-dtype") || has("--limit-mm-per-prompt") || has("--language-model-only"); } export function scanLocalServers(): Map { const found = new Map(); let pids: string[] = []; try { pids = readdirSync("/proc"); } catch { return found; // not Linux } for (const pid of pids) { if (!/^\d+$/.test(pid)) continue; try { const args = readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean); const bin = (args[0] ?? "").split("/").pop() ?? ""; const isLlama = bin.startsWith("llama-server"); if (!isLlama && !looksLikeVllmProcess(args) && !looksLikeOpenAiEngine(args)) continue; const port = portFromArgs(args); if (port === undefined) continue; // OpenAI-compatible wrappers expose no GGUF flags; only the port and the // vision-relevant args parsed above (acceptsImages) apply. const parsed = parseServerArgs(args); found.set(port, { port, ...parsed }); } catch { // process vanished between readdir and read — ignore } } return found; } // ── HTTP client (node:http, no fetch) ────────────────────────────────────── function httpRequest( method: string, url: string, body?: string, timeoutMs = 2000, apiKey?: string, ): Promise { return new Promise((resolve, reject) => { const parsed = new URL(url); const headers: http.OutgoingHttpHeaders = { Accept: "application/json, text/plain", "User-Agent": "pi-llama-infra/1.0", }; if (apiKey) headers.Authorization = `Bearer ${apiKey}`; if (body) { headers["Content-Type"] = "application/json"; headers["Content-Length"] = Buffer.byteLength(body).toString(); } const options: http.RequestOptions = { hostname: parsed.hostname, port: parsed.port || 80, path: parsed.pathname + parsed.search, method, headers, timeout: timeoutMs, family: 4, agent: new http.Agent({ keepAlive: false, maxSockets: 1 }), }; const req = http.request(options, (res) => { const chunks: Buffer[] = []; res.on("data", (chunk: Buffer) => chunks.push(chunk)); res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf-8"), contentType: res.headers["content-type"], }), ); }); req.on("error", (err: Error) => reject(err)); req.on("timeout", () => { req.destroy(); reject(new Error(`Timeout after ${timeoutMs}ms`)); }); if (body) req.write(body); req.end(); }); } export async function httpGet(url: string, timeoutMs: number, apiKey?: string): Promise { try { return await httpRequest("GET", url, undefined, timeoutMs, apiKey); } catch (err) { const code = (err as NodeJS.ErrnoException).code; if (code === "ECONNREFUSED" || code === "ENOTFOUND") throw err; await new Promise((r) => setTimeout(r, 250)); return await httpRequest("GET", url, undefined, timeoutMs, apiKey); } } export function isNetworkError(msg: string): boolean { return /timeout|socket hang up|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH/i.test(msg); } // ── Model display names ──────────────────────────────────────────────────── export function cleanModelName(rawId: string): string { // Shared suffix-stripping regexes. Applied to the base name (before the // tag) so they work whether or not a tag follows. const suffixes: RegExp[] = [ /\.(gguf|ggml)(?:\.tar)?$/i, /[-_.][Ff](?:16|32)$/i, /[-_.][Qq]2_[Kk]$/i, /[-_.][Qq]3_[Kk]_[SLM]$/i, /[-_.][Qq]4_[01]|[-_.][Qq]4_[Kk]_[MS]$/i, /[-_.][Qq]5_[0K]_[MS]$/i, /[-_.][Qq]6_[Kk](_[A-Z])?$/i, /[-_.][Qq]8_[0O]$/i, /-(?:Uncensored|Alpaca|Instruct|Chat|Turbo|Base|MoE|UD)(?:-[a-zA-Z0-9]+)*$/i, /-(?:v[0-9.]+)(?:-[a-zA-Z0-9]+)*$/i, /-+$/, ]; function stripSuffixes(name: string): string { let n = name; for (const re of suffixes) n = n.replace(re, ""); return n.trim(); } // If the id contains a (host:port) tag, handle it specially: the tag may // contain a "/" (e.g. "Qwen (gpu-host:8000/)") or the id may have a path // prefix (e.g. "/models/Qwen (gpu-host:8000)"). In both cases, extract the // tag, clean the base name (strip path + quant suffixes), and re-attach // the tag with the "/" stripped. const tagMatch = rawId.match(/\(([^)]*)\)$/); if (tagMatch) { const base = rawId.slice(0, tagMatch.index); const tagInner = tagMatch[1]; // Clean the base name: strip path prefix, trim, then quant/format suffixes. let cleanBase = base.split("/").pop()?.trim() || base; cleanBase = stripSuffixes(cleanBase); // Clean the tag: strip trailing slashes. const cleanTag = tagInner.replace(/\/+$/, ""); // If the base is empty, don't prepend a space before the tag. const prefix = cleanBase ? `${cleanBase} ` : ""; return `${prefix}(${cleanTag})`; } let name = rawId.split("/").pop() || rawId; name = stripSuffixes(name); return name || rawId; } // ── /props + LM Studio catalog ───────────────────────────────────────────── /** * halogen (owned_by:"halogen") is a proprietary OpenAI-compatible engine: it * publishes neither /props nor meta.n_ctx in /v1/models — the context lives * only in /health (context / slot_ctx), and its vision capability only in * /health.vision.enabled. Without this the registration chain falls back to * 32768 and pi compacts way too early, and images are rejected because pi * thinks the model is text-only. Probe /health once per scan and graft the * numbers onto the model entries. */ export interface EngineHealth { /** Served context (slot_ctx preferred, else context). */ context?: number; /** /health.vision.enabled — halogen serves images when true. */ vision?: boolean; } /** Read GET /health (halogen-flash-server). Returns undefined when the server * has no /health or does not answer. */ export async function fetchEngineHealth( baseUrl: string, timeoutMs: number, apiKey?: string, ): Promise { const rootUrl = baseUrl.replace(/\/v1\/?$/, ""); // /health probes the engine (seconds when busy); the generic 2 s discovery // timeout is too short for it. Give this one a floor of 8 s. const probeMs = Math.max(timeoutMs, 8000); try { const { status, body } = await httpGet(`${rootUrl}/health`, probeMs, apiKey); if (status < 200 || status >= 300) return undefined; const h = JSON.parse(body) as { context?: number; slot_ctx?: number; vision?: { enabled?: boolean }; }; const out: EngineHealth = {}; const ctx = h.slot_ctx ?? h.context; if (typeof ctx === "number" && ctx > 0) out.context = ctx; if (h.vision?.enabled === true) out.vision = true; return out; } catch { // no /health return undefined; } } export async function applyHalogenHealth( baseUrl: string, models: LlamaCppModel[], timeoutMs: number, apiKey?: string, hostKey?: string, ): Promise { const health = await fetchEngineHealth(baseUrl, timeoutMs, apiKey); let ctx = health?.context; if (hostKey) { if (ctx !== undefined) shared.halogenCtx.set(hostKey, ctx); else ctx = shared.halogenCtx.get(hostKey); // transient miss: do not regress to 32768 } if (typeof ctx === "number" && ctx > 0) { for (const m of models) { m.meta = { ...(m.meta ?? {}), n_ctx: m.meta?.n_ctx ?? ctx }; m.max_model_len = m.max_model_len ?? ctx; } } return health; } export async function fetchServerProps( baseUrl: string, timeoutMs: number, apiKey?: string, model?: string, ): Promise { const rootUrl = baseUrl.replace(/\/v1\/?$/, ""); let url = `${rootUrl}/props`; if (model !== undefined) url += `?model=${encodeURIComponent(model)}&autoload=false`; try { const { status, body } = await httpGet(url, timeoutMs, apiKey); if (status >= 200 && status < 300) return JSON.parse(body) as ServerProps; } catch { // no /props → plain OpenAI-compatible server } return undefined; } export function lmStudioKey(info: LmStudioModelInfo): string | undefined { return info.key ?? info.id; } export function lmStudioQuantName(info: LmStudioModelInfo | undefined): string | undefined { if (!info) return undefined; const q = info.quantization; if (typeof q === "string") return q || undefined; return q?.name ?? undefined; } export function lmStudioContextLength(info: LmStudioModelInfo | undefined): number | undefined { if (!info) return undefined; const loadedCtx = info.loaded_instances?.find((inst) => typeof inst.config?.context_length === "number")?.config?.context_length; return loadedCtx ?? info.max_context_length; } function isLmStudioModelInfo(value: unknown): value is LmStudioModelInfo { const m = value as LmStudioModelInfo | undefined; if (!m || typeof m !== "object") return false; return Boolean( m.key || m.id || m.display_name || m.compatibility_type || m.format || m.loaded_instances || typeof m.max_context_length === "number" || m.capabilities, ); } export async function fetchLmStudioCatalog( baseUrl: string, timeoutMs: number, apiKey?: string, ): Promise { const rootUrl = baseUrl.replace(/\/v1\/?$/, ""); for (const path of ["/api/v1/models", "/api/v0/models"]) { try { const { status, body } = await httpGet(`${rootUrl}${path}`, timeoutMs, apiKey); if (status < 200 || status >= 300) continue; const payload = JSON.parse(body) as LmStudioModelsResponse; const models = Array.isArray(payload.models) ? payload.models : Array.isArray(payload.data) ? payload.data : undefined; if (models && models.length > 0 && models.some(isLmStudioModelInfo)) return models; } catch { // Not LM Studio, or older server without the REST metadata endpoint. } } return undefined; } function buildLmStudioCatalogMap(catalog: LmStudioModelInfo[]): Map { const map = new Map(); const add = (key: string | undefined, info: LmStudioModelInfo) => { const k = key?.trim().toLowerCase(); if (k && !map.has(k)) map.set(k, info); }; for (const info of catalog) { add(lmStudioKey(info), info); add(info.key, info); add(info.id, info); add(info.selected_variant, info); add(info.display_name, info); for (const variant of info.variants ?? []) add(variant, info); for (const inst of info.loaded_instances ?? []) add(inst.id, info); } return map; } function enrichWithLmStudioCatalog(models: LlamaCppModel[], catalog: LmStudioModelInfo[]): LlamaCppModel[] { const byKey = buildLmStudioCatalogMap(catalog); return models.map((model) => { const rawId = String(model.id ?? ""); const info = byKey.get(rawId.toLowerCase()) ?? byKey.get(baseName(rawId).toLowerCase()); if (!info) return model; return { ...model, display_name: info.display_name ?? model.display_name, lmStudio: info }; }); } // ── Server kind detection ────────────────────────────────────────────────── export async function detectServerKind( _baseUrl: string, models: LlamaCppModel[], props: ServerProps | undefined, lmStudioCatalog?: LmStudioModelInfo[], ): Promise { const serverName = String(props?.server?.name ?? props?.build_info ?? "").toLowerCase(); if (serverName.startsWith("luce")) return "lucebox"; if (serverName.startsWith("dwarf") || serverName.startsWith("ds4")) return "dwarfstar"; if (models.length > 0 && models[0].owned_by === "zinc") return "zinc"; if (models.some((m) => String(m.owned_by ?? "").toLowerCase().includes("lmstudio"))) return "lmstudio"; if (lmStudioCatalog && lmStudioCatalog.length > 0) return "lmstudio"; if (models.some((m) => m.lmStudio)) return "lmstudio"; // vLLM exposes owned_by:"vllm" + max_model_len (no /props, no meta.n_ctx). if (models.some((m) => String(m.owned_by ?? "").toLowerCase() === "vllm")) return "vllm"; if (models.length > 0) return "llamacpp"; return "unknown"; } // ── DwarfStar probe ──────────────────────────────────────────────────────── export async function probeDs4Server( ep: EndpointResult, timeoutMs: number, apiKey?: string, ): Promise { const chatUrl = `${ep.baseUrl}/chat/completions`; const tryProbe = async (model?: string): Promise => { const reqBody: Record = { messages: [{ role: "user", content: "ping" }], max_tokens: 1, }; if (model) reqBody.model = model; try { const res = await httpRequest("POST", chatUrl, JSON.stringify(reqBody), timeoutMs, apiKey); if (res.status < 200 || res.status >= 300) return null; const p = JSON.parse(res.body) as { model?: string }; return p.model ?? model ?? "ds4-model"; } catch { return null; } }; try { let modelName = await tryProbe(); if (!modelName) modelName = await tryProbe("ping"); if (modelName) { ep.ok = true; ep.error = undefined; ep.loading = false; ep.server = "dwarfstar"; ep.mode = "single"; ep.models = [{ id: modelName, name: modelName }]; ep.meta.set(modelName, { quant: extractQuantTag(modelName) }); return ep; } ep.error = "ds4 probe: no response from /v1/chat/completions"; return ep; } catch (error) { ep.error = `ds4 probe: ${error instanceof Error ? error.message : String(error)}`; return ep; } } // ── Per-model metadata ───────────────────────────────────────────────────── export function buildModelMetadata( rawId: string, entry: LlamaCppModel, props: ServerProps | undefined, local: LocalServerInfo | undefined, kind?: ServerKind | "unknown", health?: { vision?: boolean }, ): ModelMetadata { const meta: ModelMetadata = {}; // Quant: GGUF filename / router id / vLLM `root` / LM Studio metadata. const sourcePath = entry.path ?? entry.root ?? props?.model_path ?? rawId; meta.quant = extractQuantTag(sourcePath) ?? lmStudioQuantName(entry.lmStudio)?.toUpperCase(); // Vision. if (entry.architecture?.input_modalities?.includes("image")) meta.vision = true; if (meta.vision === undefined && props?.modalities?.vision === true) meta.vision = true; if (!meta.vision && (entry.lmStudio?.capabilities?.vision === true || entry.lmStudio?.type === "vlm")) { meta.vision = true; } // halogen publishes neither /props nor architecture.input_modalities: its // vision capability lives only in /health.vision.enabled. if (!meta.vision && health?.vision) meta.vision = true; if (!meta.vision && local?.hasMmproj) meta.vision = true; // Local engine args are ground truth for OpenAI-compatible engines: // --language-model-only makes vLLM answer 400 'At most 0 image(s)', while // --limit-mm-per-prompt {"image":N} with N>0 means images are accepted. if (local?.acceptsImages !== undefined) meta.vision = local.acceptsImages; // Drafter. const argsInfo = entry.status?.args ? parseServerArgs(entry.status.args) : undefined; if (argsInfo?.draft) meta.drafter = argsInfo.draft; else if (argsInfo?.hasDraft) meta.drafter = "draft model"; if (!meta.drafter) { const draftPath = props?.model?.draft_path ?? props?.draft_path; if (draftPath) meta.drafter = baseName(draftPath); else if (props?.default_generation_settings?.speculative === true) meta.drafter = "speculative"; } if (!meta.drafter && local?.draft) meta.drafter = local.draft; else if (!meta.drafter && local?.hasDraft) meta.drafter = "draft model"; // Manual overrides win: vLLM publishes neither modalities nor drafter. const override = modelOptionsFor(rawId); if (override?.vision !== undefined) meta.vision = override.vision; if (override?.drafter !== undefined) meta.drafter = override.drafter; // KV cache quantization is GGUF/llama.cpp-specific (vLLM has no /props, no cache flags). if (kind === "llamacpp" || props !== undefined) { meta.cacheK = argsInfo?.cacheK ?? local?.cacheK ?? props?.cache_type_k?.toLowerCase(); meta.cacheV = argsInfo?.cacheV ?? local?.cacheV ?? props?.cache_type_v?.toLowerCase(); } // Router / LM Studio load status. if (entry.status?.value) meta.routerStatus = entry.status.value; else if (entry.lmStudio?.state && entry.lmStudio.state !== "loaded") meta.routerStatus = entry.lmStudio.state; return meta; } // ── One endpoint scan ────────────────────────────────────────────────────── export async function fetchModelsFromEndpoint( srv: ServerConfig, port: number, settings: SettingsConfig, localServers: Map, ): Promise { const baseUrl = `http://${srv.host}:${port}/v1`; const ep: EndpointResult = { serverId: srv.id, host: srv.host, label: serverLabel(srv), port, baseUrl, server: "auto", mode: "unknown", models: [], meta: new Map(), ok: false, }; const started = Date.now(); try { const { status, body } = await httpGet(`${baseUrl}/models`, settings.discoveryTimeoutMs, srv.apiKey); ep.latencyMs = Date.now() - started; if (status < 200 || status >= 300) { ep.error = `${status}: ${(body || "unknown error").slice(0, 200)}`; ep.loading = status === 503; if (srv.probeDs4 && !ep.loading) return probeDs4Server(ep, settings.discoveryTimeoutMs, srv.apiKey); return ep; } const payload = JSON.parse(body) as LlamaCppModelsResponse; let models = payload.data ?? []; const nameMap = new Map(); if (Array.isArray(payload.models)) { for (const m of payload.models) { if (m.model && m.name) nameMap.set(m.model, m.name); } } let props = await fetchServerProps(baseUrl, settings.discoveryTimeoutMs, srv.apiKey); const lmStudioCatalog = props ? undefined : await fetchLmStudioCatalog(baseUrl, settings.discoveryTimeoutMs, srv.apiKey); if (lmStudioCatalog) models = enrichWithLmStudioCatalog(models, lmStudioCatalog); const isRouterShape = models.some((m) => m.path !== undefined || m.status !== undefined); const routerProps = props?.role === "router"; const mode = lmStudioCatalog ? models.length > 1 ? "router" : "single" : isRouterShape || routerProps ? "router" : "single"; if (mode === "router") props = undefined; const kind = await detectServerKind(baseUrl, models, props, lmStudioCatalog); let engineHealth: EngineHealth | undefined; if (kind === "llamacpp" && props === undefined && models.some((m) => String(m.owned_by ?? "").toLowerCase() === "halogen")) { engineHealth = await applyHalogenHealth(baseUrl, models, settings.discoveryTimeoutMs, srv.apiKey, `${srv.host}:${port}`); } if (kind === "lucebox") { props = (await fetchServerProps(baseUrl, settings.discoveryTimeoutMs, srv.apiKey)) ?? props; } const local = isLocalHost(srv.host) && settings.detectVision ? localServers.get(port) : undefined; for (const model of models) { const rawId = String(model.id ?? ""); let modelProps = props; if (mode === "router" && !model.architecture?.input_modalities && model.status?.value === "loaded") { modelProps = (await fetchServerProps(baseUrl, settings.discoveryTimeoutMs, srv.apiKey, rawId)) ?? undefined; } ep.meta.set(rawId, buildModelMetadata(rawId, model, modelProps, local, kind, engineHealth)); } ep.server = kind; ep.mode = mode; ep.models = models; ep.nameMap = nameMap; ep.props = props; ep.ok = true; return ep; } catch (err) { ep.latencyMs = Date.now() - started; const msg = err instanceof Error ? err.message : String(err); ep.error = msg; if (srv.probeDs4 && !isNetworkError(msg)) { return probeDs4Server(ep, settings.discoveryTimeoutMs, srv.apiKey); } return ep; } } // ── Signature + scan-all ─────────────────────────────────────────────────── export function modelsSignature(endpoints: EndpointResult[]): string { return endpoints .map((r) => { const models = r.models .filter((m) => { if (r.server === "lmstudio") return true; const st = r.meta.get(String(m.id ?? ""))?.routerStatus; return !st || st === "loaded" || shared.rIncludeUnloaded; }) .map((m) => `${m.id}:${r.meta.get(String(m.id ?? ""))?.routerStatus ?? ""}`) .sort() .join(","); return `${r.host}:${r.port}:${r.ok ? r.server : r.loading ? "loading" : "down"}:${models}`; }) .join("|"); } export async function scanAllServers(config: { servers: ServerConfig[]; settings: SettingsConfig }): Promise { shared.rIncludeUnloaded = config.settings.includeUnloadedRouterModels; const enabled = config.servers.filter((s) => s.enabled && s.ports.length > 0); const localServers = config.settings.detectVision ? scanLocalServers() : new Map(); const tasks: Array> = []; for (const srv of enabled) { for (const port of srv.ports) { tasks.push(fetchModelsFromEndpoint(srv, port, config.settings, localServers)); } } const endpoints = await Promise.all(tasks); const serversUp = new Set(endpoints.filter((e) => e.ok).map((e) => e.serverId)).size; const totalModels = endpoints.reduce((acc, e) => acc + (e.ok ? e.models.length : 0), 0); return { endpoints, totalModels, serversUp, serversTotal: enabled.length }; }