import { readdirSync } from "node:fs"; import { join } from "node:path"; import type { FastifyInstance } from "fastify"; import type { PiModel } from "../pi/list-models.js"; import { isPrincipalContext, requirePermission } from "../auth/authorization.js"; export type ListModelsFn = () => Promise; /** * List YAML workspace configs found in /workspaces/*.yaml. * Returns [{name, file}] — no database credentials or secrets. */ export function listWorkspaces(harnessDir: string): { name: string; file: string }[] { const dir = join(harnessDir, "workspaces"); let entries: string[]; try { entries = readdirSync(dir); } catch { return []; } return entries .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")) .map((f) => ({ name: f.replace(/\.ya?ml$/, ""), file: f })); } export function metaRoutes( app: FastifyInstance, deps: { harnessDir: string; listModels?: ListModelsFn }, ): void { app.get("/models", async (request, reply) => { if (!isPrincipalContext(requirePermission(request, reply, "session.use"))) return reply; const fn = deps.listModels ?? (async () => []); try { return { models: await fn() }; } catch (error) { app.log.warn({ component: "pi-model-list", errorType: error instanceof Error ? error.name : typeof error, }, "Pi model listing failed"); // Graceful fallback: Pi may not be running; don't crash the server. return { models: [] as PiModel[] }; } }); }