chore: drop superseded admin-panel agent-config prototype after rebase onto main

main now ships the canonical org-scoped agent-config implementation
(OrganizationAgentRole/OrganizationAgentSkill + hub/src/agent/configuration.ts
+ hub/src/agent/skillStore.ts per ADR-0018, and envelope-encrypted
OrganizationProviderConnection per ADR-0024). The earlier admin-panel
prototype (OrgModel/OrgRole/simple ProviderConnection baseUrl+authToken,
modelRoutes.ts, agentConfig.ts, migration 20260710120000, admin-web
models/roles pages) is superseded and clashes with main's schema; drop it.

Follow-up still needed: rewire admin-web SPA to the new config APIs
(+layout.svelte nav still lists models/roles, RoleCard.svelte unused).
This commit is contained in:
2026-07-13 21:44:07 +08:00
parent 9c33a4e9b9
commit b1ddf32238
7 changed files with 0 additions and 1219 deletions
-252
View File
@@ -1,252 +0,0 @@
/**
* 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<void> {
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;
}
-5
View File
@@ -12,7 +12,6 @@ import {
import { registerAccessRoutes } from "./accessRoutes.js";
import { registerExplorerRoutes } from "./explorerRoutes.js";
import { registerMembersRoutes } from "./membersRoutes.js";
import { registerModelRoutes } from "./modelRoutes.js";
import { registerSessionsAndUsageRoutes } from "./sessionsRoutes.js";
import { registerTeamsRoutes } from "./teamsRoutes.js";
import { registerProviderConnectionRoutes } from "./providerConnectionRoutes.js";
@@ -106,10 +105,6 @@ export async function registerOrgRoutes(app: FastifyInstance, config: OrgRouteCo
prisma: config.prisma,
sessionSecret: config.sessionSecret,
});
await registerModelRoutes(app, {
prisma: config.prisma,
sessionSecret: config.sessionSecret,
});
await registerSessionsAndUsageRoutes(app, {
prisma: config.prisma,
sessionSecret: config.sessionSecret,
-386
View File
@@ -1,386 +0,0 @@
/**
* Org-scoped agent config: provider connection, enabled models, and role
* presets (ADR-0017 / ADR-0021).
*
* Roles and models are **data** (ADR-0017): admin/teacher-defined, persisted
* per organization. The runtime registry (`settings/runtime.ts`) reads these
* rows, falling back to env-based defaults when an org has none configured.
*
* Provider credential storage is a pilot placeholder: the spec leaves key/base
* URL encryption, rotation and resolver mechanism OPEN (secret-control-plane).
* BYOK tokens are stored as opaque strings here; platform-managed credentials
* are resolved from process env, not stored in this table.
*/
import type { PrismaClient } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { assertSupportedRoleTools } from "../agent/roleTools.js";
export interface ProviderConnectionRow {
readonly organizationId: string;
readonly providerId: string;
readonly mode: "BYOK" | "PLATFORM_MANAGED";
readonly baseUrl: string | null;
readonly hasAuthToken: boolean;
readonly createdAt: string;
readonly updatedAt: string;
}
export async function getProviderConnection(
prisma: PrismaClient,
organizationId: string,
): Promise<ProviderConnectionRow> {
const row = await prisma.organizationProviderConnection.findUnique({
where: { organizationId },
});
if (row === null) {
return {
organizationId,
providerId: "openrouter",
mode: "PLATFORM_MANAGED",
baseUrl: null,
hasAuthToken: false,
createdAt: "",
updatedAt: "",
};
}
return {
organizationId: row.organizationId,
providerId: row.providerId,
mode: row.mode,
baseUrl: row.baseUrl,
hasAuthToken: row.authToken !== null && row.authToken !== "",
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
export async function upsertProviderConnection(
prisma: PrismaClient,
input: {
readonly organizationId: string;
readonly providerId?: string | undefined;
readonly mode: "BYOK" | "PLATFORM_MANAGED";
readonly baseUrl?: string | null | undefined;
readonly authToken?: string | null | undefined;
},
): Promise<ProviderConnectionRow> {
const providerId = input.providerId ?? "openrouter";
if (providerId.trim() === "") throw new Error("providerId is required");
const baseUrl =
input.baseUrl === undefined || input.baseUrl === null
? null
: input.baseUrl.trim() === ""
? null
: input.baseUrl.trim();
const authToken =
input.authToken === undefined || input.authToken === null
? undefined
: input.authToken === ""
? null
: input.authToken;
const row = await prisma.organizationProviderConnection.upsert({
where: { organizationId: input.organizationId },
create: {
organizationId: input.organizationId,
providerId,
mode: input.mode,
baseUrl,
...(authToken !== undefined ? { authToken } : {}),
},
update: {
providerId,
mode: input.mode,
baseUrl,
...(authToken !== undefined ? { authToken } : {}),
},
});
return {
organizationId: row.organizationId,
providerId: row.providerId,
mode: row.mode,
baseUrl: row.baseUrl,
hasAuthToken: row.authToken !== null && row.authToken !== "",
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
export interface OrgModelRow {
readonly id: string;
readonly modelId: string;
readonly label: string;
readonly toolCapable: boolean;
readonly sortKey: string;
readonly createdAt: string;
readonly updatedAt: string;
}
export async function listOrgModels(
prisma: PrismaClient,
organizationId: string,
): Promise<readonly OrgModelRow[]> {
const rows = await prisma.orgModel.findMany({
where: { organizationId },
orderBy: [{ sortKey: "asc" }, { createdAt: "asc" }],
});
return rows.map(toModelRow);
}
export async function createOrgModel(
prisma: PrismaClient,
input: {
readonly organizationId: string;
readonly modelId: string;
readonly label: string;
readonly toolCapable?: boolean | undefined;
readonly sortKey?: string | undefined;
},
): Promise<OrgModelRow> {
const modelId = requireNonEmpty(input.modelId, "modelId");
const label = requireNonEmpty(input.label, "label");
const existing = await prisma.orgModel.findUnique({
where: { organizationId_modelId: { organizationId: input.organizationId, modelId } },
select: { id: true },
});
if (existing !== null) {
throw new Error(`model already exists: ${modelId}`);
}
const row = await prisma.orgModel.create({
data: {
organizationId: input.organizationId,
modelId,
label,
...(input.toolCapable !== undefined ? { toolCapable: input.toolCapable } : {}),
...(input.sortKey !== undefined ? { sortKey: input.sortKey } : {}),
},
});
return toModelRow(row);
}
export async function updateOrgModel(
prisma: PrismaClient,
input: {
readonly organizationId: string;
readonly id: string;
readonly label?: string | undefined;
readonly toolCapable?: boolean | undefined;
readonly sortKey?: string | undefined;
readonly modelId?: string | undefined;
},
): Promise<OrgModelRow> {
const row = await prisma.orgModel.findUnique({ where: { id: input.id } });
if (row === null || row.organizationId !== input.organizationId) {
throw new Error(`model not found: ${input.id}`);
}
const updated = await prisma.orgModel.update({
where: { id: row.id },
data: {
...(input.label !== undefined ? { label: requireNonEmpty(input.label, "label") } : {}),
...(input.toolCapable !== undefined ? { toolCapable: input.toolCapable } : {}),
...(input.sortKey !== undefined ? { sortKey: input.sortKey } : {}),
...(input.modelId !== undefined
? { modelId: requireNonEmpty(input.modelId, "modelId") }
: {}),
},
});
return toModelRow(updated);
}
export async function deleteOrgModel(
prisma: PrismaClient,
input: { readonly organizationId: string; readonly id: string },
): Promise<{ readonly deleted: true; readonly id: string }> {
const row = await prisma.orgModel.findUnique({ where: { id: input.id } });
if (row === null || row.organizationId !== input.organizationId) {
throw new Error(`model not found: ${input.id}`);
}
await prisma.orgModel.delete({ where: { id: row.id } });
return { deleted: true as const, id: row.id };
}
export interface OrgRoleRow {
readonly id: string;
readonly roleId: string;
readonly label: string;
readonly defaultModelId: string | null;
readonly systemPrompt: string | null;
readonly tools: readonly string[] | null;
readonly createdAt: string;
readonly updatedAt: string;
}
export async function listOrgRoles(
prisma: PrismaClient,
organizationId: string,
): Promise<readonly OrgRoleRow[]> {
const rows = await prisma.orgRole.findMany({
where: { organizationId },
orderBy: [{ roleId: "asc" }],
});
return rows.map(toRoleRow);
}
export async function createOrgRole(
prisma: PrismaClient,
input: {
readonly organizationId: string;
readonly roleId: string;
readonly label: string;
readonly defaultModelId?: string | null | undefined;
readonly systemPrompt?: string | null | undefined;
readonly tools?: readonly string[] | null | undefined;
},
): Promise<OrgRoleRow> {
const roleId = requireNonEmpty(input.roleId, "roleId");
const label = requireNonEmpty(input.label, "label");
const existing = await prisma.orgRole.findUnique({
where: { organizationId_roleId: { organizationId: input.organizationId, roleId } },
select: { id: true },
});
if (existing !== null) {
throw new Error(`role already exists: ${roleId}`);
}
const tools = normalizeTools(input.tools);
const row = await prisma.orgRole.create({
data: {
organizationId: input.organizationId,
roleId,
label,
...(input.defaultModelId !== undefined && input.defaultModelId !== null
? { defaultModelId: input.defaultModelId }
: {}),
...(input.defaultModelId === null ? { defaultModelId: null } : {}),
...(input.systemPrompt !== undefined && input.systemPrompt !== null
? { systemPrompt: input.systemPrompt }
: {}),
...(input.systemPrompt === null ? { systemPrompt: null } : {}),
...(tools !== undefined ? { tools: tools ?? Prisma.DbNull } : {}),
},
});
return toRoleRow(row);
}
export async function updateOrgRole(
prisma: PrismaClient,
input: {
readonly organizationId: string;
readonly id: string;
readonly label?: string | undefined;
readonly defaultModelId?: string | null | undefined;
readonly systemPrompt?: string | null | undefined;
readonly tools?: readonly string[] | null | undefined;
readonly roleId?: string | undefined;
},
): Promise<OrgRoleRow> {
const row = await prisma.orgRole.findUnique({ where: { id: input.id } });
if (row === null || row.organizationId !== input.organizationId) {
throw new Error(`role not found: ${input.id}`);
}
const tools = normalizeTools(input.tools);
const updated = await prisma.orgRole.update({
where: { id: row.id },
data: {
...(input.label !== undefined ? { label: requireNonEmpty(input.label, "label") } : {}),
...(input.defaultModelId !== undefined && input.defaultModelId !== null
? { defaultModelId: input.defaultModelId }
: {}),
...(input.defaultModelId === null ? { defaultModelId: null } : {}),
...(input.systemPrompt !== undefined && input.systemPrompt !== null
? { systemPrompt: input.systemPrompt }
: {}),
...(input.systemPrompt === null ? { systemPrompt: null } : {}),
...(tools !== undefined ? { tools: tools ?? Prisma.DbNull } : {}),
...(input.roleId !== undefined ? { roleId: requireNonEmpty(input.roleId, "roleId") } : {}),
},
});
return toRoleRow(updated);
}
export async function deleteOrgRole(
prisma: PrismaClient,
input: { readonly organizationId: string; readonly id: string },
): Promise<{ readonly deleted: true; readonly id: string }> {
const row = await prisma.orgRole.findUnique({ where: { id: input.id } });
if (row === null || row.organizationId !== input.organizationId) {
throw new Error(`role not found: ${input.id}`);
}
await prisma.orgRole.delete({ where: { id: row.id } });
return { deleted: true as const, id: row.id };
}
function toModelRow(
row: {
readonly id: string;
readonly modelId: string;
readonly label: string;
readonly toolCapable: boolean;
readonly sortKey: string;
readonly createdAt: Date;
readonly updatedAt: Date;
},
): OrgModelRow {
return {
id: row.id,
modelId: row.modelId,
label: row.label,
toolCapable: row.toolCapable,
sortKey: row.sortKey,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
function toRoleRow(
row: {
readonly id: string;
readonly roleId: string;
readonly label: string;
readonly defaultModelId: string | null;
readonly systemPrompt: string | null;
readonly tools: unknown;
readonly createdAt: Date;
readonly updatedAt: Date;
},
): OrgRoleRow {
return {
id: row.id,
roleId: row.roleId,
label: row.label,
defaultModelId: row.defaultModelId,
systemPrompt: row.systemPrompt,
tools: parseTools(row.tools),
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
/**
* Normalize a tools input into a value safe for prisma `tools` (Json?) column.
* - `undefined` ⇒ omit (leave unchanged): returns `undefined`.
* - `null` ⇒ unrestricted role (SQL NULL): returns `null`.
* - array ⇒ validated against the supported role tool set (fails fast).
*/
function normalizeTools(
tools: readonly string[] | null | undefined,
): readonly string[] | null | undefined {
if (tools === undefined) return undefined;
if (tools === null) return null;
const list = [...tools];
assertSupportedRoleTools(list);
return list;
}
function parseTools(tools: unknown): readonly string[] | null {
if (tools === null) return null;
if (!Array.isArray(tools)) return null;
return tools.filter((t): t is string => typeof t === "string");
}
function requireNonEmpty(value: string, label: string): string {
const trimmed = value.trim();
if (trimmed === "") throw new Error(`${label} is required`);
return trimmed;
}