225 lines · 7.0 KB · TypeScript
RawDownload
// CRITICALimport type { Hono } from "hono";import { connect } from "node:net";import { hostname } from "node:os";import type { AppContext } from "../types/context";import type { HealthResponse, SystemConfigResponse } from "../types/models";import { getGpuInfo } from "../services/gpu"; /** * Register system routes. * @param app - Hono application. * @param context - App context. */export const registerSystemRoutes = (app: Hono, context: AppContext): void => {  /**   * Check if a TCP service is reachable.   * @param host - Hostname.   * @param port - Port number.   * @param timeoutMs - Timeout in ms.   * @returns Promise resolving to availability.   */  const checkService = (host: string, port: number, timeoutMs = 1000): Promise<boolean> => {    return new Promise((resolve) => {      const socket = connect(port, host);      const timer = setTimeout(() => {        socket.destroy();        resolve(false);      }, timeoutMs);      socket.once("connect", () => {        clearTimeout(timer);        socket.end();        resolve(true);      });      socket.once("error", () => {        clearTimeout(timer);        resolve(false);      });    });  };   app.get("/health", async (ctx) => {    const current = await context.processManager.findInferenceProcess(context.config.inference_port);    let inferenceReady = false;    if (current) {      try {        const controller = new AbortController();        const timeout = setTimeout(() => controller.abort(), 5000);        const response = await fetch(`http://localhost:${context.config.inference_port}/health`, {          signal: controller.signal,        });        clearTimeout(timeout);        inferenceReady = response.status === 200;      } catch {        inferenceReady = false;      }    }     const payload: HealthResponse = {      status: "ok",      version: "0.3.1",      inference_ready: inferenceReady,      backend_reachable: inferenceReady,      running_model: current ? (current.served_model_name ?? current.model_path ?? null) : null,    };    return ctx.json(payload);  });   app.get("/status", async (ctx) => {    const current = await context.processManager.findInferenceProcess(context.config.inference_port);    return ctx.json({      running: Boolean(current),      process: current,      inference_port: context.config.inference_port,      launching: context.launchState.getLaunchingRecipeId(),    });  });   app.get("/gpus", async (ctx) => {    const gpus = getGpuInfo();    return ctx.json({      count: gpus.length,      gpus,    });  });   app.get("/config", async (ctx) => {    const services: Array<{ name: string; port: number; internal_port: number; protocol: string; status: string; description?: string | null }> = [];    services.push({      name: "Controller",      port: context.config.port,      internal_port: context.config.port,      protocol: "http",      status: "running",      description: "FastAPI model lifecycle manager",    });     let inferenceStatus = "unknown";    try {      const current = await context.processManager.findInferenceProcess(context.config.inference_port);      if (current) {        const controller = new AbortController();        const timeout = setTimeout(() => controller.abort(), 2000);        const response = await fetch(`http://localhost:${context.config.inference_port}/health`, {          signal: controller.signal,        });        clearTimeout(timeout);        inferenceStatus = response.status === 200 ? "running" : "error";      } else {        inferenceStatus = "stopped";      }    } catch {      inferenceStatus = "stopped";    }     services.push({      name: "vLLM/SGLang",      port: context.config.inference_port,      internal_port: context.config.inference_port,      protocol: "http",      status: inferenceStatus,      description: "Inference backend (vLLM or SGLang)",    });     let litellmStatus = "unknown";    try {      const controller = new AbortController();      const timeout = setTimeout(() => controller.abort(), 10_000);      const masterKey = process.env["LITELLM_MASTER_KEY"] ?? "sk-master";      const response = await fetch("http://localhost:4100/health", {        headers: { Authorization: `Bearer ${masterKey}` },        signal: controller.signal,      });      clearTimeout(timeout);      if (response.status === 200) {        const data = (await response.json()) as { healthy_count?: number };        litellmStatus = (data.healthy_count ?? 0) > 0 ? "running" : "degraded";      } else {        litellmStatus = "error";      }    } catch {      litellmStatus = "stopped";    }     services.push({      name: "LiteLLM",      port: 4100,      internal_port: 4000,      protocol: "http",      status: litellmStatus,      description: "API gateway and load balancer",    });     const postgresReachable = await checkService("localhost", 5432);    services.push({      name: "PostgreSQL",      port: 5432,      internal_port: 5432,      protocol: "tcp",      status: postgresReachable ? "running" : "stopped",      description: "Database for LiteLLM",    });     const redisReachable = await checkService("localhost", 6379);    services.push({      name: "Redis",      port: 6379,      internal_port: 6379,      protocol: "tcp",      status: redisReachable ? "running" : "stopped",      description: "Cache and rate limiting",    });     let prometheusStatus = "unknown";    try {      const controller = new AbortController();      const timeout = setTimeout(() => controller.abort(), 2000);      const response = await fetch("http://localhost:9090/-/healthy", { signal: controller.signal });      clearTimeout(timeout);      prometheusStatus = response.status === 200 ? "running" : "error";    } catch {      prometheusStatus = "stopped";    }    services.push({      name: "Prometheus",      port: 9090,      internal_port: 9090,      protocol: "http",      status: prometheusStatus,      description: "Metrics collection",    });     const frontendReachable = await checkService("localhost", 3000);    services.push({      name: "Frontend",      port: 3000,      internal_port: 3000,      protocol: "http",      status: frontendReachable ? "running" : "stopped",      description: "Next.js web UI",    });     const payload: SystemConfigResponse = {      config: {        host: context.config.host,        port: context.config.port,        inference_port: context.config.inference_port,        api_key_configured: Boolean(context.config.api_key),        models_dir: context.config.models_dir,        data_dir: context.config.data_dir,        db_path: context.config.db_path,        sglang_python: context.config.sglang_python ?? null,        tabby_api_dir: context.config.tabby_api_dir ?? null,      },      services,      environment: {        controller_url: `http://${hostname()}:${context.config.port}`,        inference_url: `http://${hostname()}:${context.config.inference_port}`,        litellm_url: `http://${hostname()}:4100`,        frontend_url: `http://${hostname()}:3000`,      },    };     return ctx.json(payload);  });};