// CRITICALimport { Hono } from "hono";import { swaggerUI } from "@hono/swagger-ui";import { cors } from "hono/cors";import type { AppContext } from "../types/context";import { isHttpStatus } from "../core/errors";import { registerChatsRoutes } from "../routes/chats";import { registerLifecycleRoutes } from "../routes/lifecycle";import { registerLogsRoutes } from "../routes/logs";import { registerMcpRoutes } from "../routes/mcp";import { registerModelsRoutes } from "../routes/models";import { registerMonitoringRoutes } from "../routes/monitoring";import { registerProxyRoutes } from "../routes/proxy";import { registerProxyRoutesDebug } from "../routes/proxy-debug";import { registerSystemRoutes } from "../routes/system";import { registerUsageRoutes } from "../routes/usage";import { registerTokenizationRoutes } from "../routes/tokenization"; /** * Create the Hono application. * @param context - App context. * @returns Hono app instance. */export const createApp = (context: AppContext): Hono => { const app = new Hono(); app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"], })); app.use("*", async (ctx, next) => { const skip = new Set(["/health", "/metrics", "/events", "/status", "/api/docs", "/api/spec"]); if (!skip.has(ctx.req.path)) { context.logger.debug(`${ctx.req.method} ${ctx.req.path}`); } await next(); }); // Register all routes registerSystemRoutes(app, context); registerModelsRoutes(app, context); registerLifecycleRoutes(app, context); registerChatsRoutes(app, context); registerLogsRoutes(app, context); registerMonitoringRoutes(app, context); registerMcpRoutes(app, context); registerProxyRoutes(app, context); registerProxyRoutesDebug(app, context); registerUsageRoutes(app, context); registerTokenizationRoutes(app, context); // OpenAPI documentation endpoints app.get("/api/spec", (ctx) => { return ctx.json({ openapi: "3.1.0", info: { title: "vLLM Studio API", version: "0.3.1", description: "Model lifecycle management for vLLM, SGLang, and TabbyAPI inference servers", }, servers: [ { url: `http://localhost:${context.config.port}`, description: "Local development server", }, ], paths: { "/health": { get: { summary: "Health check", description: "Check if the controller and inference backend are healthy", responses: { "200": { description: "Service is healthy", content: { "application/json": { schema: { type: "object", properties: { status: { type: "string", enum: ["ok"] }, version: { type: "string" }, inference_ready: { type: "boolean" }, backend_reachable: { type: "boolean" }, running_model: { type: ["string", "null"] }, }, }, }, }, }, }, }, }, "/status": { get: { summary: "Get status", description: "Get current status of the inference backend", responses: { "200": { description: "Status information", }, }, }, }, "/gpus": { get: { summary: "List GPUs", description: "Get GPU information including memory, utilization, temperature", responses: { "200": { description: "GPU list", }, }, }, }, "/recipes": { get: { summary: "List recipes", description: "Get all model launch recipes", responses: { "200": { description: "Recipe list", }, }, }, post: { summary: "Create recipe", description: "Create a new model launch recipe", responses: { "201": { description: "Recipe created", }, }, }, }, "/launch/{recipe_id}": { post: { summary: "Launch model", description: "Launch a model from a recipe", parameters: [ { name: "recipe_id", in: "path", required: true, schema: { type: "string" }, }, ], responses: { "200": { description: "Model launched", }, }, }, }, "/chats": { get: { summary: "List chat sessions", responses: { "200": { description: "Chat list", }, }, }, post: { summary: "Create chat session", responses: { "201": { description: "Chat created", }, }, }, }, }, }); }); app.get("/api/docs", swaggerUI({ url: "/api/spec" })); app.notFound((ctx) => ctx.json({ detail: "Not Found" }, { status: 404 })); app.onError((error, ctx) => { if (isHttpStatus(error)) { return ctx.json({ detail: error.detail }, { status: error.status }); } context.logger.error("Unhandled error", { error: String(error) }); return ctx.json({ detail: "Internal Server Error" }, { status: 500 }); }); return app;};
// CRITICAL
import { Hono } from "hono";
import { swaggerUI } from "@hono/swagger-ui";
import { cors } from "hono/cors";
import type { AppContext } from "../types/context";
import { isHttpStatus } from "../core/errors";
import { registerChatsRoutes } from "../routes/chats";
import { registerLifecycleRoutes } from "../routes/lifecycle";
import { registerLogsRoutes } from "../routes/logs";
import { registerMcpRoutes } from "../routes/mcp";
import { registerModelsRoutes } from "../routes/models";
import { registerMonitoringRoutes } from "../routes/monitoring";
import { registerProxyRoutes } from "../routes/proxy";
import { registerProxyRoutesDebug } from "../routes/proxy-debug";
import { registerSystemRoutes } from "../routes/system";
import { registerUsageRoutes } from "../routes/usage";
import { registerTokenizationRoutes } from "../routes/tokenization";
/**
* Create the Hono application.
* @param context - App context.
* @returns Hono app instance.
*/
export const createApp = (context: AppContext): Hono => {
const app = new Hono();
app.use("*", cors({
origin: "*",
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["*"],
}));
app.use("*", async (ctx, next) => {
const skip = new Set(["/health", "/metrics", "/events", "/status", "/api/docs", "/api/spec"]);
if (!skip.has(ctx.req.path)) {
context.logger.debug(`${ctx.req.method} ${ctx.req.path}`);
}
await next();
});
// Register all routes
registerSystemRoutes(app, context);
registerModelsRoutes(app, context);
registerLifecycleRoutes(app, context);
registerChatsRoutes(app, context);
registerLogsRoutes(app, context);
registerMonitoringRoutes(app, context);
registerMcpRoutes(app, context);
registerProxyRoutes(app, context);
registerProxyRoutesDebug(app, context);
registerUsageRoutes(app, context);
registerTokenizationRoutes(app, context);
// OpenAPI documentation endpoints
app.get("/api/spec", (ctx) => {
return ctx.json({
openapi: "3.1.0",
info: {
title: "vLLM Studio API",
version: "0.3.1",
description: "Model lifecycle management for vLLM, SGLang, and TabbyAPI inference servers",
},
servers: [
{
url: `http://localhost:${context.config.port}`,
description: "Local development server",
},
],
paths: {
"/health": {
get: {
summary: "Health check",
description: "Check if the controller and inference backend are healthy",
responses: {
"200": {
description: "Service is healthy",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: { type: "string", enum: ["ok"] },
version: { type: "string" },
inference_ready: { type: "boolean" },
backend_reachable: { type: "boolean" },
running_model: { type: ["string", "null"] },
},
},
},
},
},
},
},
},
"/status": {
get: {
summary: "Get status",
description: "Get current status of the inference backend",
responses: {
"200": {
description: "Status information",
},
},
},
},
"/gpus": {
get: {
summary: "List GPUs",
description: "Get GPU information including memory, utilization, temperature",
responses: {
"200": {
description: "GPU list",
},
},
},
},
"/recipes": {
get: {
summary: "List recipes",
description: "Get all model launch recipes",
responses: {
"200": {
description: "Recipe list",
},
},
},
post: {
summary: "Create recipe",
description: "Create a new model launch recipe",
responses: {
"201": {
description: "Recipe created",
},
},
},
},
"/launch/{recipe_id}": {
post: {
summary: "Launch model",
description: "Launch a model from a recipe",
parameters: [
{
name: "recipe_id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
"200": {
description: "Model launched",
},
},
},
},
"/chats": {
get: {
summary: "List chat sessions",
responses: {
"200": {
description: "Chat list",
},
},
},
post: {
summary: "Create chat session",
responses: {
"201": {
description: "Chat created",
},
},
},
},
},
});
});
app.get("/api/docs", swaggerUI({ url: "/api/spec" }));
app.notFound((ctx) => ctx.json({ detail: "Not Found" }, { status: 404 }));
app.onError((error, ctx) => {
if (isHttpStatus(error)) {
return ctx.json({ detail: error.detail }, { status: error.status });
}
context.logger.error("Unhandled error", { error: String(error) });
return ctx.json({ detail: "Internal Server Error" }, { status: 500 });
});
return app;
};