import type { FastifyInstance } from "fastify"; import type { AppConfig } from "../config.js"; import type { Settings } from "../settings/settings-store.js"; import { listWorkspaces } from "./meta.js"; import type { PrincipalContext } from "../auth/principal.js"; import { isPrincipalContext, requirePermission } from "../auth/authorization.js"; import { splitCanonicalModelId, type RuntimeModelCatalog, } from "../models/runtime-model-catalog.js"; /** Merge only workspace and runtime-thinking preferences; model defaults belong to modelCatalog. */ export function effectiveSettings( cfg: AppConfig, stored: Settings, modelCatalog?: RuntimeModelCatalog, ): Settings { const workspaces = listWorkspaces(cfg.harnessDir); const selected = modelCatalog?.defaultInteraction ? splitCanonicalModelId(modelCatalog.defaultInteraction) : undefined; return { workspace: stored.workspace ?? workspaces[0]?.name, ...(selected ?? {}), thinking: cfg.defaults.thinking ?? stored.thinking, }; } export function settingsRoutes( app: FastifyInstance, deps: { cfg: AppConfig; getSettings: (principal: PrincipalContext) => Promise; }, ): void { app.get("/settings", async (req, reply) => { const principal = requirePermission(req, reply, "session.use"); if (!isPrincipalContext(principal)) return principal; try { return await deps.getSettings(principal); } catch { return reply.code(503).send({ error: "settings storage is unavailable" }); } }); app.put("/settings", async (req, reply) => { const principal = requirePermission(req, reply, "settings.manage"); if (!isPrincipalContext(principal)) return principal; const b = (req.body ?? {}) as Settings; try { // Retain this endpoint as a validating compatibility surface for older clients, but do // not write anonymous users' choices to shared server storage. return await deps.getSettings(principal); } catch { return reply.code(503).send({ error: "settings storage is unavailable" }); } }); }