/** * Org-admin routes for agent config: provider connection, enabled models, and * role presets (ADR-0017 / ADR-0021). Mounted under `/api/org/:orgSlug`. */ import type { PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; import { createOrgModel, createOrgRole, deleteOrgModel, deleteOrgRole, getProviderConnection, listOrgModels, listOrgRoles, updateOrgModel, updateOrgRole, upsertProviderConnection, } from "../../org/agentConfig.js"; import { requireOrgRole, type GuardDeps } from "../auth/guards.js"; import { handleRouteError } from "../errors.js"; const PROVIDER_MODES = ["BYOK", "PLATFORM_MANAGED"] as const; type ProviderMode = (typeof PROVIDER_MODES)[number]; export async function registerModelRoutes( app: FastifyInstance, config: { readonly prisma: PrismaClient; readonly sessionSecret: string }, ): Promise { const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; // --- Provider connection --------------------------------------------- app.get("/api/org/:orgSlug/provider-connection", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return await getProviderConnection(config.prisma, auth.organization.id); } catch (err) { return handleRouteError(reply, err); } }); app.put("/api/org/:orgSlug/provider-connection", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = request.body as { providerId?: unknown; mode?: unknown; baseUrl?: unknown; authToken?: unknown; }; const mode = parseProviderMode(body.mode); if (mode === null) { return reply.status(400).send({ error: { code: "bad_request", message: "mode must be BYOK or PLATFORM_MANAGED" }, }); } const connection = await upsertProviderConnection(config.prisma, { organizationId: auth.organization.id, mode, ...(typeof body.providerId === "string" ? { providerId: body.providerId } : {}), ...(body.baseUrl === null || typeof body.baseUrl === "string" ? { baseUrl: body.baseUrl as string | null } : {}), ...(body.authToken === null || typeof body.authToken === "string" ? { authToken: body.authToken as string | null } : {}), }); return connection; } catch (err) { return handleRouteError(reply, err); } }); // --- Models ---------------------------------------------------------- app.get("/api/org/:orgSlug/models", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return { models: await listOrgModels(config.prisma, auth.organization.id) }; } catch (err) { return handleRouteError(reply, err); } }); app.post("/api/org/:orgSlug/models", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = request.body as { modelId?: unknown; label?: unknown; toolCapable?: unknown; sortKey?: unknown; }; if (typeof body.modelId !== "string" || typeof body.label !== "string") { return reply.status(400).send({ error: { code: "bad_request", message: "modelId and label are required" }, }); } const model = await createOrgModel(config.prisma, { organizationId: auth.organization.id, modelId: body.modelId, label: body.label, ...(typeof body.toolCapable === "boolean" ? { toolCapable: body.toolCapable } : {}), ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}), }); return reply.status(201).send(model); } catch (err) { return handleRouteError(reply, err); } }); app.patch("/api/org/:orgSlug/models/:modelId", async (request, reply) => { try { const { orgSlug, modelId } = request.params as { orgSlug: string; modelId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = request.body as { label?: unknown; toolCapable?: unknown; sortKey?: unknown; modelId?: unknown; }; return await updateOrgModel(config.prisma, { organizationId: auth.organization.id, id: modelId, ...(typeof body.label === "string" ? { label: body.label } : {}), ...(typeof body.toolCapable === "boolean" ? { toolCapable: body.toolCapable } : {}), ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}), ...(typeof body.modelId === "string" ? { modelId: body.modelId } : {}), }); } catch (err) { return handleRouteError(reply, err); } }); app.delete("/api/org/:orgSlug/models/:modelId", async (request, reply) => { try { const { orgSlug, modelId } = request.params as { orgSlug: string; modelId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return await deleteOrgModel(config.prisma, { organizationId: auth.organization.id, id: modelId, }); } catch (err) { return handleRouteError(reply, err); } }); // --- Roles ----------------------------------------------------------- app.get("/api/org/:orgSlug/roles", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return { roles: await listOrgRoles(config.prisma, auth.organization.id) }; } catch (err) { return handleRouteError(reply, err); } }); app.post("/api/org/:orgSlug/roles", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = request.body as { roleId?: unknown; label?: unknown; defaultModelId?: unknown; systemPrompt?: unknown; tools?: unknown; }; if (typeof body.roleId !== "string" || typeof body.label !== "string") { return reply.status(400).send({ error: { code: "bad_request", message: "roleId and label are required" }, }); } const role = await createOrgRole(config.prisma, { organizationId: auth.organization.id, roleId: body.roleId, label: body.label, ...(typeof body.defaultModelId === "string" ? { defaultModelId: body.defaultModelId } : {}), ...(body.defaultModelId === null ? { defaultModelId: null } : {}), ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}), ...(body.systemPrompt === null ? { systemPrompt: null } : {}), ...(Array.isArray(body.tools) ? { tools: body.tools as readonly string[] } : {}), ...(body.tools === null ? { tools: null } : {}), }); return reply.status(201).send(role); } catch (err) { return handleRouteError(reply, err); } }); app.patch("/api/org/:orgSlug/roles/:roleId", async (request, reply) => { try { const { orgSlug, roleId } = request.params as { orgSlug: string; roleId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = request.body as { label?: unknown; defaultModelId?: unknown; systemPrompt?: unknown; tools?: unknown; roleId?: unknown; }; return await updateOrgRole(config.prisma, { organizationId: auth.organization.id, id: roleId, ...(typeof body.label === "string" ? { label: body.label } : {}), ...(typeof body.defaultModelId === "string" ? { defaultModelId: body.defaultModelId } : {}), ...(body.defaultModelId === null ? { defaultModelId: null } : {}), ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}), ...(body.systemPrompt === null ? { systemPrompt: null } : {}), ...(Array.isArray(body.tools) ? { tools: body.tools as readonly string[] } : {}), ...(body.tools === null ? { tools: null } : {}), ...(typeof body.roleId === "string" ? { roleId: body.roleId } : {}), }); } catch (err) { return handleRouteError(reply, err); } }); app.delete("/api/org/:orgSlug/roles/:roleId", async (request, reply) => { try { const { orgSlug, roleId } = request.params as { orgSlug: string; roleId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return await deleteOrgRole(config.prisma, { organizationId: auth.organization.id, id: roleId, }); } catch (err) { return handleRouteError(reply, err); } }); } function parseProviderMode(value: unknown): ProviderMode | null { if (typeof value !== "string") return null; return (PROVIDER_MODES as readonly string[]).includes(value) ? (value as ProviderMode) : null; }