History

refactor(repo): adopt src/features convention, controller RouteRegistrar, shared validation

- frontend: src/features/{agent,recipes,discover,settings,usage,plugins,setup,logs,dashboard}; src/ui reduced to primitives; src/components retired; agent trees merged (lib/agent + hooks/agent + app/agent/_components -> features/agent) - controller: RouteRegistrar type across modules, core/validation.ts body helpers, studio provider routes split out - shared API route helpers (app/api/_lib/route-helpers.ts), themes data/API split, lib/types consolidated to recipes.ts + system.ts - structure validator rewritten to enforce the new layering Verified: check:quality (incl. production build), controller integration 49/49, frontend e2e 155/155, contracts check.

seroxdesign committed 5 months ago

Commit c9d1ea51 parent 8d0aacf334 files · +2556 −2495Browse files

Showing the changes to controller/src/modules (12 of 334 files). Show all

Unified · Split

12 files
  1. controller/src/modules/engines/routes.ts +15 −22
  2. controller/src/modules/models/routes.ts +4 −7
  3. controller/src/modules/proxy/interfaces.ts +1 −4
  4. controller/src/modules/proxy/openai-routes.ts +2 −3
  5. controller/src/modules/proxy/routes.ts +2 −3
  6. controller/src/modules/proxy/tokenization-routes.ts +2 −3
  7. controller/src/modules/studio/provider-routes.ts +119 −0
  8. controller/src/modules/studio/routes.ts +4 −116
  9. controller/src/modules/system/logs-routes.ts +2 −3
  10. controller/src/modules/system/metrics-routes.ts +2 −2
  11. controller/src/modules/system/routes.ts +4 −7
  12. controller/src/modules/system/usage-routes.ts +2 −2
controller/src/modules/engines/routes.ts+15−22
@@ -1,7 +1,7 @@
1import type { Hono } from "hono";1import type { RouteRegistrar } from "../../http/route-registrar";
2import type { AppContext } from "../../types/context";
3import { delay } from "../../core/async";2import { delay } from "../../core/async";
4import { HttpStatus, badRequest, notFound, serviceUnavailable } from "../../core/errors";3import { HttpStatus, badRequest, notFound, serviceUnavailable } from "../../core/errors";
4import { optionalEnum, optionalStringArray, parseJsonObjectBody } from "../../core/validation";
5import { observeControllerFunction } from "../../core/function-observability";5import { observeControllerFunction } from "../../core/function-observability";
6import { parseRecipe } from "../models/recipes/recipe-serializer";6import { parseRecipe } from "../models/recipes/recipe-serializer";
7import { Event } from "../system/event-manager";7import { Event } from "../system/event-manager";
@@ -40,37 +40,30 @@ const resolveHfToken = ( · 32 unmodified lines
40 return bodyToken || headerToken || envToken;40 return bodyToken || headerToken || envToken;
41};41};
42 42
43const RUNTIME_JOB_BACKENDS = ["vllm", "sglang", "llamacpp", "mlx", "cuda", "rocm"] as const;
44const RUNTIME_JOB_TYPES = ["install", "update", "download", "inspect"] as const;
45
43const parseRuntimeJobBody = async (ctx: {46const parseRuntimeJobBody = async (ctx: {
44 req: { json: () => Promise<unknown> };47 req: { json: () => Promise<unknown> };
45}): Promise<{48}): Promise<{
46 backend?: "vllm" | "sglang" | "llamacpp" | "mlx" | "cuda" | "rocm";49 backend?: (typeof RUNTIME_JOB_BACKENDS)[number];
47 targetId?: string;50 targetId?: string;
48 type?: "install" | "update" | "download" | "inspect";51 type?: (typeof RUNTIME_JOB_TYPES)[number];
49 command?: string;52 command?: string;
50 args?: string[];53 args?: string[];
51 version?: string;54 version?: string;
52 preferBundled?: boolean;55 preferBundled?: boolean;
53}> => {56}> => {
54 const body = await ctx.req.json().catch(() => ({}));57 const record = await parseJsonObjectBody(ctx);
55 if (!body || typeof body !== "object" || Array.isArray(body)) throw badRequest("Invalid payload");58 const backend = optionalEnum(record, "backend", RUNTIME_JOB_BACKENDS);
56 const record = body as Record<string, unknown>;59 const type = optionalEnum(record, "type", RUNTIME_JOB_TYPES, "job type");
57 const backend = typeof record["backend"] === "string" ? record["backend"] : undefined;60 const args = optionalStringArray(record, "args");
58 if (backend && !["vllm", "sglang", "llamacpp", "mlx", "cuda", "rocm"].includes(backend))
59 throw badRequest("Invalid backend");
60 const type = typeof record["type"] === "string" ? record["type"] : undefined;
61 if (type && !["install", "update", "download", "inspect"].includes(type))
62 throw badRequest("Invalid job type");
63 const args = Array.isArray(record["args"]) ? record["args"] : undefined;
64 if (args?.some((value) => typeof value !== "string"))
65 throw badRequest("args must be an array of strings");
66 return {61 return {
67 ...(backend62 ...(backend ? { backend } : {}),
68 ? { backend: backend as "vllm" | "sglang" | "llamacpp" | "mlx" | "cuda" | "rocm" }
69 : {}),
70 ...(typeof record["targetId"] === "string" ? { targetId: record["targetId"] } : {}),63 ...(typeof record["targetId"] === "string" ? { targetId: record["targetId"] } : {}),
71 ...(type ? { type: type as "install" | "update" | "download" | "inspect" } : {}),64 ...(type ? { type } : {}),
72 ...(typeof record["command"] === "string" ? { command: record["command"] } : {}),65 ...(typeof record["command"] === "string" ? { command: record["command"] } : {}),
73 ...(args ? { args: args as string[] } : {}),66 ...(args ? { args } : {}),
74 ...(typeof record["version"] === "string" ? { version: record["version"] } : {}),67 ...(typeof record["version"] === "string" ? { version: record["version"] } : {}),
75 ...(typeof record["prefer_bundled"] === "boolean"68 ...(typeof record["prefer_bundled"] === "boolean"
76 ? { preferBundled: record["prefer_bundled"] }69 ? { preferBundled: record["prefer_bundled"] }
@@ -78,7 +71,7 @@ const parseRuntimeJobBody = async (ctx: { · 1 unmodified line
78 };71 };
79};72};
80 73
81export const registerEngineRoutes = (app: Hono, context: AppContext): void => {74export const registerEngineRoutes: RouteRegistrar = (app, context) => {
82 const launchAbortControllers = new Map<string, AbortController>();75 const launchAbortControllers = new Map<string, AbortController>();
83 76
84 app.get("/recipes", async (ctx) => {77 app.get("/recipes", async (ctx) => {
controller/src/modules/models/routes.ts+4−7
@@ -1,8 +1,7 @@
1import type { Hono } from "hono";
2import { basename, dirname, resolve } from "node:path";1import { basename, dirname, resolve } from "node:path";
3import { existsSync } from "node:fs";2import { existsSync } from "node:fs";
4import { homedir } from "node:os";3import { homedir } from "node:os";
5import type { AppContext } from "../../types/context";4import type { RouteRegistrar } from "../../http/route-registrar";
6import type { Recipe } from "../models/types";5import type { Recipe } from "../models/types";
7 6
8/**7/**
@@ -28,13 +27,11 @@ interface OpenAIModelList { · 19 unmodified lines
28import { buildModelInfo, discoverModelDirectories } from "./model-browser";27import { buildModelInfo, discoverModelDirectories } from "./model-browser";
29import { notFound } from "../../core/errors";28import { notFound } from "../../core/errors";
30import { observeControllerFunction } from "../../core/function-observability";29import { observeControllerFunction } from "../../core/function-observability";
30import { parseBooleanFlag } from "../../core/validation";
31import { fetchInference } from "../../services/inference/inference-client";31import { fetchInference } from "../../services/inference/inference-client";
32 32
33function isMockInferenceEnabled(): boolean {33function isMockInferenceEnabled(): boolean {
34 const raw = process.env["VLLM_STUDIO_MOCK_INFERENCE"];34 return parseBooleanFlag(process.env["VLLM_STUDIO_MOCK_INFERENCE"]);
35 if (!raw) return false;
36 const normalized = String(raw).trim().toLowerCase();
37 return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
38}35}
39 36
40function recipeMetadata(recipe: Recipe): Record<string, unknown> | undefined {37function recipeMetadata(recipe: Recipe): Record<string, unknown> | undefined {
@@ -45,7 +42,7 @@ function recipeMetadata(recipe: Recipe): Record<string, unknown> | undefined { · 4 unmodified lines
45 return metadata as Record<string, unknown>;42 return metadata as Record<string, unknown>;
46}43}
47 44
48export const registerModelsRoutes = (app: Hono, context: AppContext): void => {45export const registerModelsRoutes: RouteRegistrar = (app, context) => {
49 app.get("/v1/models", async (ctx) => {46 app.get("/v1/models", async (ctx) => {
50 const recipes = context.stores.recipeStore.list();47 const recipes = context.stores.recipeStore.list();
51 const current = await observeControllerFunction(48 const current = await observeControllerFunction(
controller/src/modules/proxy/interfaces.ts+1−4
@@ -1,4 +1 @@
1import type { Hono } from "hono";1export type { RouteRegistrar as ProxyRouteRegistrar } from "../../http/route-registrar";
2import type { AppContext } from "../../types/context";
3
4export type ProxyRouteRegistrar = (app: Hono, context: AppContext) => void;
controller/src/modules/proxy/openai-routes.ts+2−3
@@ -1,9 +1,8 @@
1import { performance } from "node:perf_hooks";1import { performance } from "node:perf_hooks";
2import type { Hono } from "hono";
3import { HttpStatus, notFound, serviceUnavailable } from "../../core/errors";2import { HttpStatus, notFound, serviceUnavailable } from "../../core/errors";
4import { isRecipeRunning } from "../models/recipes/recipe-matching";3import { isRecipeRunning } from "../models/recipes/recipe-matching";
5import { buildSseHeaders } from "../../http/sse";4import { buildSseHeaders } from "../../http/sse";
6import type { AppContext } from "../../types/context";5import type { RouteRegistrar } from "../../http/route-registrar";
7import type { Recipe } from "../models/types";6import type { Recipe } from "../models/types";
8import { getDefaultReasoningParser } from "../engines/process/model-runtime-defaults";7import { getDefaultReasoningParser } from "../engines/process/model-runtime-defaults";
9import { buildInferenceUrl } from "../../services/inference/inference-client";8import { buildInferenceUrl } from "../../services/inference/inference-client";
@@ -89,7 +88,7 @@ const shouldBufferImplicitReasoningContent = ( · 79 unmodified lines
89 );88 );
90};89};
91 90
92export const registerOpenAIRoutes = (app: Hono, context: AppContext): void => {91export const registerOpenAIRoutes: RouteRegistrar = (app, context) => {
93 const nonRunningModelWarnings = new Map<string, NonRunningModelWarningState>();92 const nonRunningModelWarnings = new Map<string, NonRunningModelWarningState>();
94 93
95 const warnNonRunningModel = (details: {94 const warnNonRunningModel = (details: {
controller/src/modules/proxy/routes.ts+2−3
@@ -1,9 +1,8 @@
1import type { Hono } from "hono";1import type { RouteRegistrar } from "../../http/route-registrar";
2import type { AppContext } from "../../types/context";
3import { registerOpenAIRoutes } from "./openai-routes";2import { registerOpenAIRoutes } from "./openai-routes";
4import { registerTokenizationRoutes } from "./tokenization-routes";3import { registerTokenizationRoutes } from "./tokenization-routes";
5 4
6export const registerAllProxyRoutes = (app: Hono, context: AppContext): void => {5export const registerAllProxyRoutes: RouteRegistrar = (app, context) => {
7 registerOpenAIRoutes(app, context);6 registerOpenAIRoutes(app, context);
8 registerTokenizationRoutes(app, context);7 registerTokenizationRoutes(app, context);
9};8};
controller/src/modules/proxy/tokenization-routes.ts+2−3
@@ -1,9 +1,8 @@
1import type { Hono } from "hono";
2import { observeControllerFunction } from "../../core/function-observability";1import { observeControllerFunction } from "../../core/function-observability";
3import type { AppContext } from "../../types/context";2import type { RouteRegistrar } from "../../http/route-registrar";
4import { fetchInference } from "../../services/inference/inference-client";3import { fetchInference } from "../../services/inference/inference-client";
5 4
6export const registerTokenizationRoutes = (app: Hono, context: AppContext): void => {5export const registerTokenizationRoutes: RouteRegistrar = (app, context) => {
7 app.post("/v1/tokenize", async (ctx) => {6 app.post("/v1/tokenize", async (ctx) => {
8 const current = await observeControllerFunction(context, "tokenize.findInferenceProcess", () =>7 const current = await observeControllerFunction(context, "tokenize.findInferenceProcess", () =>
9 context.processManager.findInferenceProcess(context.config.inference_port)8 context.processManager.findInferenceProcess(context.config.inference_port)
controller/src/modules/studio/provider-routes.ts+119−0
@@ -0,0 +1,119 @@
1import { badRequest, notFound } from "../../core/errors";
2import { parseJsonObjectBody } from "../../core/validation";
3import type { RouteRegistrar } from "../../http/route-registrar";
4import { savePersistedConfig, type ProviderConfig } from "../../config/persisted-config";
5
6/** CRUD for external inference providers plus model discovery across them. */
7export const registerStudioProviderRoutes: RouteRegistrar = (app, context) => {
8 app.get("/studio/providers", async (ctx) => {
9 const providers = context.config.providers.map((p) => ({
10 id: p.id,
11 name: p.name,
12 base_url: p.base_url,
13 enabled: p.enabled,
14 has_api_key: Boolean(p.api_key),
15 }));
16 return ctx.json({ providers });
17 });
18
19 app.post("/studio/providers", async (ctx) => {
20 const body = await parseJsonObjectBody(ctx);
21
22 const id = typeof body["id"] === "string" ? body["id"].trim().toLowerCase() : "";
23 const name = typeof body["name"] === "string" ? body["name"].trim() : "";
24 const baseUrl = typeof body["base_url"] === "string" ? body["base_url"].trim() : "";
25 const apiKey = typeof body["api_key"] === "string" ? body["api_key"].trim() : "";
26 const enabled = typeof body["enabled"] === "boolean" ? body["enabled"] : true;
27
28 if (!id) throw badRequest("id is required");
29 if (!name) throw badRequest("name is required");
30 if (!baseUrl) throw badRequest("base_url is required");
31
32 const existing = context.config.providers.find((p) => p.id === id);
33 if (existing) throw badRequest(`Provider "${id}" already exists`);
34
35 const provider: ProviderConfig = { id, name, base_url: baseUrl, api_key: apiKey, enabled };
36 const providers = [...context.config.providers, provider];
37 savePersistedConfig(context.config.data_dir, { providers });
38 context.config.providers = providers;
39
40 return ctx.json({
41 success: true,
42 provider: { id, name, base_url: baseUrl, enabled, has_api_key: Boolean(apiKey) },
43 });
44 });
45
46 app.put("/studio/providers/:id", async (ctx) => {
47 const providerId = ctx.req.param("id");
48 const body = await ctx.req.json().catch(() => ({}));
49 if (!body || typeof body !== "object") throw badRequest("Invalid payload");
50
51 const index = context.config.providers.findIndex((p) => p.id === providerId);
52 if (index < 0) throw notFound(`Provider "${providerId}" not found`);
53
54 const current = context.config.providers[index];
55 if (!current) throw notFound(`Provider "${providerId}" not found`);
56
57 const name = typeof body.name === "string" ? body.name.trim() : current.name;
58 const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : current.base_url;
59 const apiKey = typeof body.api_key === "string" ? body.api_key.trim() : current.api_key;
60 const enabled = typeof body.enabled === "boolean" ? body.enabled : current.enabled;
61
62 const updated: ProviderConfig = {
63 id: providerId,
64 name,
65 base_url: baseUrl,
66 api_key: apiKey,
67 enabled,
68 };
69 const providers = [...context.config.providers];
70 providers[index] = updated;
71 savePersistedConfig(context.config.data_dir, { providers });
72 context.config.providers = providers;
73
74 return ctx.json({
75 success: true,
76 provider: { id: providerId, name, base_url: baseUrl, enabled, has_api_key: Boolean(apiKey) },
77 });
78 });
79
80 app.delete("/studio/providers/:id", async (ctx) => {
81 const providerId = ctx.req.param("id");
82 const index = context.config.providers.findIndex((p) => p.id === providerId);
83 if (index < 0) throw notFound(`Provider "${providerId}" not found`);
84
85 const providers = context.config.providers.filter((p) => p.id !== providerId);
86 savePersistedConfig(context.config.data_dir, { providers });
87 context.config.providers = providers;
88
89 return ctx.json({ success: true });
90 });
91
92 // Fetch models from all configured providers
93 app.get("/studio/provider-models", async (ctx) => {
94 const enabledProviders = context.config.providers.filter((p) => p.enabled && p.api_key);
95 const results: Array<{ provider: string; models: Array<{ id: string; name?: string }> }> = [];
96
97 await Promise.all(
98 enabledProviders.map(async (provider) => {
99 try {
100 const url = `${provider.base_url.replace(/\/+$/, "")}/v1/models`;
101 const res = await fetch(url, {
102 headers: { Authorization: `Bearer ${provider.api_key}` },
103 signal: AbortSignal.timeout(10_000),
104 });
105 if (!res.ok) return;
106 const data = (await res.json()) as { data?: Array<{ id?: string }> };
107 const models = (data.data ?? [])
108 .filter((m) => typeof m.id === "string" && m.id.length > 0)
109 .map((m) => ({ id: m.id as string }));
110 results.push({ provider: provider.id, models });
111 } catch {
112 // skip unreachable providers
113 }
114 })
115 );
116
117 return ctx.json({ providers: results });
118 });
119};
controller/src/modules/studio/routes.ts+4−116
@@ -1,4 +1,3 @@
1import type { Hono } from "hono";
2import { cpus, freemem, totalmem, platform, arch, release } from "node:os";1import { cpus, freemem, totalmem, platform, arch, release } from "node:os";
3import {2import {
4 existsSync,3 existsSync,
@@ -12,7 +11,8 @@ import { · 7 unmodified lines
12} from "node:fs";11} from "node:fs";
13import { basename, resolve, sep } from "node:path";12import { basename, resolve, sep } from "node:path";
14import { badRequest, notFound } from "../../core/errors";13import { badRequest, notFound } from "../../core/errors";
15import type { AppContext } from "../../types/context";14import type { RouteRegistrar } from "../../http/route-registrar";
15import { registerStudioProviderRoutes } from "./provider-routes";
16import { getGpuInfo } from "../system/platform/gpu";16import { getGpuInfo } from "../system/platform/gpu";
17import type { GpuInfo } from "../models/types";17import type { GpuInfo } from "../models/types";
18import { discoverModelDirectories, estimateWeightsSizeBytes } from "../models/model-browser";18import { discoverModelDirectories, estimateWeightsSizeBytes } from "../models/model-browser";
@@ -21,7 +21,6 @@ import { · 2 unmodified lines
21 getPersistedConfigPath,21 getPersistedConfigPath,
22 loadPersistedConfig,22 loadPersistedConfig,
23 savePersistedConfig,23 savePersistedConfig,
24 type ProviderConfig,
25} from "../../config/persisted-config";24} from "../../config/persisted-config";
26import { getVllmRuntimeInfo } from "../engines/runtimes/vllm-runtime";25import { getVllmRuntimeInfo } from "../engines/runtimes/vllm-runtime";
27 26
@@ -111,7 +110,7 @@ const parseUiPreferencesUpdate = (value: unknown): Record<string, string> | null · 83 unmodified lines
111 return clean;110 return clean;
112};111};
113 112
114export const registerStudioRoutes = (app: Hono, context: AppContext): void => {113export const registerStudioRoutes: RouteRegistrar = (app, context) => {
115 const buildSettingsPayload = (): {114 const buildSettingsPayload = (): {
116 config_path: string;115 config_path: string;
117 persisted: {116 persisted: {
@@ -318,116 +317,5 @@ export const registerStudioRoutes = (app: Hono, context: AppContext): void => { · 200 unmodified lines
318 return ctx.json({ success: true, target });317 return ctx.json({ success: true, target });
319 });318 });
320 319
321 app.get("/studio/providers", async (ctx) => {320 registerStudioProviderRoutes(app, context);
322 const providers = context.config.providers.map((p) => ({
323 id: p.id,
324 name: p.name,
325 base_url: p.base_url,
326 enabled: p.enabled,
327 has_api_key: Boolean(p.api_key),
328 }));
329 return ctx.json({ providers });
330 });
331
332 app.post("/studio/providers", async (ctx) => {
333 const body = await ctx.req.json().catch(() => ({}));
334 if (!body || typeof body !== "object") throw badRequest("Invalid payload");
335
336 const id = typeof body.id === "string" ? body.id.trim().toLowerCase() : "";
337 const name = typeof body.name === "string" ? body.name.trim() : "";
338 const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : "";
339 const apiKey = typeof body.api_key === "string" ? body.api_key.trim() : "";
340 const enabled = typeof body.enabled === "boolean" ? body.enabled : true;
341
342 if (!id) throw badRequest("id is required");
343 if (!name) throw badRequest("name is required");
344 if (!baseUrl) throw badRequest("base_url is required");
345
346 const existing = context.config.providers.find((p) => p.id === id);
347 if (existing) throw badRequest(`Provider "${id}" already exists`);
348
349 const provider: ProviderConfig = { id, name, base_url: baseUrl, api_key: apiKey, enabled };
350 const providers = [...context.config.providers, provider];
351 savePersistedConfig(context.config.data_dir, { providers });
352 context.config.providers = providers;
353
354 return ctx.json({
355 success: true,
356 provider: { id, name, base_url: baseUrl, enabled, has_api_key: Boolean(apiKey) },
357 });
358 });
359
360 app.put("/studio/providers/:id", async (ctx) => {
361 const providerId = ctx.req.param("id");
362 const body = await ctx.req.json().catch(() => ({}));
363 if (!body || typeof body !== "object") throw badRequest("Invalid payload");
364
365 const index = context.config.providers.findIndex((p) => p.id === providerId);
366 if (index < 0) throw notFound(`Provider "${providerId}" not found`);
367
368 const current = context.config.providers[index];
369 if (!current) throw notFound(`Provider "${providerId}" not found`);
370
371 const name = typeof body.name === "string" ? body.name.trim() : current.name;
372 const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : current.base_url;
373 const apiKey = typeof body.api_key === "string" ? body.api_key.trim() : current.api_key;
374 const enabled = typeof body.enabled === "boolean" ? body.enabled : current.enabled;
375
376 const updated: ProviderConfig = {
377 id: providerId,
378 name,
379 base_url: baseUrl,
380 api_key: apiKey,
381 enabled,
382 };
383 const providers = [...context.config.providers];
384 providers[index] = updated;
385 savePersistedConfig(context.config.data_dir, { providers });
386 context.config.providers = providers;
387
388 return ctx.json({
389 success: true,
390 provider: { id: providerId, name, base_url: baseUrl, enabled, has_api_key: Boolean(apiKey) },
391 });
392 });
393
394 app.delete("/studio/providers/:id", async (ctx) => {
395 const providerId = ctx.req.param("id");
396 const index = context.config.providers.findIndex((p) => p.id === providerId);
397 if (index < 0) throw notFound(`Provider "${providerId}" not found`);
398
399 const providers = context.config.providers.filter((p) => p.id !== providerId);
400 savePersistedConfig(context.config.data_dir, { providers });
401 context.config.providers = providers;
402
403 return ctx.json({ success: true });
404 });
405
406 // Fetch models from all configured providers
407 app.get("/studio/provider-models", async (ctx) => {
408 const enabledProviders = context.config.providers.filter((p) => p.enabled && p.api_key);
409 const results: Array<{ provider: string; models: Array<{ id: string; name?: string }> }> = [];
410
411 await Promise.all(
412 enabledProviders.map(async (provider) => {
413 try {
414 const url = `${provider.base_url.replace(/\/+$/, "")}/v1/models`;
415 const res = await fetch(url, {
416 headers: { Authorization: `Bearer ${provider.api_key}` },
417 signal: AbortSignal.timeout(10_000),
418 });
419 if (!res.ok) return;
420 const data = (await res.json()) as { data?: Array<{ id?: string }> };
421 const models = (data.data ?? [])
422 .filter((m) => typeof m.id === "string" && m.id.length > 0)
423 .map((m) => ({ id: m.id as string }));
424 results.push({ provider: provider.id, models });
425 } catch {
426 // skip unreachable providers
427 }
428 })
429 );
430
431 return ctx.json({ providers: results });
432 });
433};321};
controller/src/modules/system/logs-routes.ts+2−3
@@ -1,9 +1,8 @@
1import type { Hono } from "hono";
2import { spawn, spawnSync } from "node:child_process";1import { spawn, spawnSync } from "node:child_process";
3import { unlinkSync } from "node:fs";2import { unlinkSync } from "node:fs";
4import { createInterface } from "node:readline";3import { createInterface } from "node:readline";
5import { PassThrough } from "node:stream";4import { PassThrough } from "node:stream";
6import type { AppContext } from "../../types/context";5import type { RouteRegistrar } from "../../http/route-registrar";
7import { badRequest, notFound } from "../../core/errors";6import { badRequest, notFound } from "../../core/errors";
8import { observeControllerFunction } from "../../core/function-observability";7import { observeControllerFunction } from "../../core/function-observability";
9import { streamAsyncStrings, buildSseHeaders, withSseHeartbeat } from "../../http/sse";8import { streamAsyncStrings, buildSseHeaders, withSseHeartbeat } from "../../http/sse";
@@ -21,7 +20,7 @@ import { · 11 unmodified lines
21 tailFileLines,20 tailFileLines,
22} from "../../core/log-files";21} from "../../core/log-files";
23 22
24export const registerLogsRoutes = (app: Hono, context: AppContext): void => {23export const registerLogsRoutes: RouteRegistrar = (app, context) => {
25 let lastCleanupAt = 0;24 let lastCleanupAt = 0;
26 25
27 const maybeCleanup = (): void => {26 const maybeCleanup = (): void => {
controller/src/modules/system/metrics-routes.ts+2−2
@@ -1,6 +1,6 @@
1import type { Hono } from "hono";
2import { performance } from "node:perf_hooks";1import { performance } from "node:perf_hooks";
3import { observeControllerFunction } from "../../core/function-observability";2import { observeControllerFunction } from "../../core/function-observability";
3import type { RouteRegistrar } from "../../http/route-registrar";
4import type { AppContext } from "../../types/context";4import type { AppContext } from "../../types/context";
5import { getGpuInfo } from "./platform/gpu";5import { getGpuInfo } from "./platform/gpu";
6import { fetchInference } from "../../services/inference/inference-client";6import { fetchInference } from "../../services/inference/inference-client";
@@ -241,7 +241,7 @@ const buildCurrentMetrics = async (context: AppContext): Promise<Record<string, · 234 unmodified lines
241 };241 };
242};242};
243 243
244export const registerMonitoringRoutes = (app: Hono, context: AppContext): void => {244export const registerMonitoringRoutes: RouteRegistrar = (app, context) => {
245 app.get("/metrics", async (_ctx) => {245 app.get("/metrics", async (_ctx) => {
246 const current = await observeControllerFunction(246 const current = await observeControllerFunction(
247 context,247 context,
controller/src/modules/system/routes.ts+4−7
@@ -1,11 +1,11 @@
1import type { Hono } from "hono";
2import { connect } from "node:net";1import { connect } from "node:net";
3import { hostname } from "node:os";2import { hostname } from "node:os";
4import { existsSync, readFileSync } from "node:fs";3import { existsSync, readFileSync } from "node:fs";
5import { join, resolve, sep } from "node:path";4import { join, resolve, sep } from "node:path";
6import type { AppContext } from "../../types/context";5import type { RouteRegistrar } from "../../http/route-registrar";
7import type { SystemConfigResponse } from "../models/types";6import type { SystemConfigResponse } from "../models/types";
8import { badRequest, notFound } from "../../core/errors";7import { badRequest, notFound } from "../../core/errors";
8import { parseJsonObjectBody } from "../../core/validation";
9import { observeControllerFunction } from "../../core/function-observability";9import { observeControllerFunction } from "../../core/function-observability";
10import { estimateWeightsSizeBytes } from "../models/model-browser";10import { estimateWeightsSizeBytes } from "../models/model-browser";
11import { getGpuInfo } from "./platform/gpu";11import { getGpuInfo } from "./platform/gpu";
@@ -21,7 +21,7 @@ import { · 9 unmodified lines
21 SYSTEM_SERVICE_CHECK_HOST,21 SYSTEM_SERVICE_CHECK_HOST,
22} from "./configs";22} from "./configs";
23 23
24export const registerSystemRoutes = (app: Hono, context: AppContext): void => {24export const registerSystemRoutes: RouteRegistrar = (app, context) => {
25 const checkService = (25 const checkService = (
26 host: string,26 host: string,
27 port: number,27 port: number,
@@ -87,10 +87,7 @@ export const registerSystemRoutes = (app: Hono, context: AppContext): void => { · 59 unmodified lines
87 });87 });
88 88
89 app.post("/vram-calculator", async (ctx) => {89 app.post("/vram-calculator", async (ctx) => {
90 const body = await ctx.req.json().catch(() => ({}));90 const body = await parseJsonObjectBody(ctx);
91 if (!body || typeof body !== "object") {
92 throw badRequest("Invalid payload");
93 }
94 91
95 const model = typeof body["model"] === "string" ? body["model"].trim() : "";92 const model = typeof body["model"] === "string" ? body["model"].trim() : "";
96 const contextLength = Number(body["context_length"] ?? 0);93 const contextLength = Number(body["context_length"] ?? 0);
controller/src/modules/system/usage-routes.ts+2−2
@@ -1,5 +1,5 @@
1import type { Hono } from "hono";
2import { observeControllerFunction } from "../../core/function-observability";1import { observeControllerFunction } from "../../core/function-observability";
2import type { RouteRegistrar } from "../../http/route-registrar";
3import type { AppContext } from "../../types/context";3import type { AppContext } from "../../types/context";
4import { getUsageFromPiSessions } from "./usage/pi-sessions";4import { getUsageFromPiSessions } from "./usage/pi-sessions";
5import { emptyResponse } from "./usage/usage-utilities";5import { emptyResponse } from "./usage/usage-utilities";
@@ -20,7 +20,7 @@ const collectKnownModels = async (context: AppContext): Promise<Set<string>> => · 14 unmodified lines
20 return knownModels;20 return knownModels;
21};21};
22 22
23export const registerUsageRoutes = (app: Hono, context: AppContext): void => {23export const registerUsageRoutes: RouteRegistrar = (app, context) => {
24 app.get("/usage", async (ctx) => {24 app.get("/usage", async (ctx) => {
25 try {25 try {
26 const knownModels = await observeControllerFunction(context, "usage.collectKnownModels", () =>26 const knownModels = await observeControllerFunction(context, "usage.collectKnownModels", () =>

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t