import type { PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; import { ProviderConnectionService } from "../../connections/providerConnections.js"; import type { LocalSecretEnvelope } from "../../security/secretEnvelope.js"; import { requireOrgRole, type GuardDeps } from "../auth/guards.js"; import { handleRouteError } from "../errors.js"; import { ProviderReadinessError, type ProviderReadinessProbe } from "../../connections/providerReadiness.js"; export interface ProviderConnectionRouteConfig { readonly prisma: PrismaClient; readonly sessionSecret: string; readonly secretEnvelope: LocalSecretEnvelope; readonly providerReadinessProbe?: ProviderReadinessProbe; } export async function registerProviderConnectionRoutes( app: FastifyInstance, config: ProviderConnectionRouteConfig, ): Promise { const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; const connections = new ProviderConnectionService( config.prisma, config.secretEnvelope, config.providerReadinessProbe, ); app.get("/api/org/:orgSlug/provider-connections", async (request, reply) => { try { const { orgSlug } = request.params as { orgSlug: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; return { connections: await connections.list(auth.organization.id), }; } catch (error) { request.log.error({ requestId: request.id, operation: "provider_connection.list", ...providerFailureFacts(error), }, "provider connection read failed"); return handleRouteError(reply, error); } }); app.put("/api/org/:orgSlug/provider-connections/:providerId", async (request, reply) => { try { const { orgSlug, providerId } = request.params as { orgSlug: string; providerId: string }; const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); if (auth === null) return; const body = parseCredentialBody(request.body); const result = await connections.rotateByok({ organizationId: auth.organization.id, providerId, actorUserId: auth.user.id, ...body, }); request.log.info({ organizationId: auth.organization.id, connectionId: result.id, providerId: result.providerId, mode: result.mode, status: result.status, secretVersion: result.activeVersion, keyId: result.keyId, }, result.created ? "provider connection created" : "provider connection rotated"); return reply.status(result.created ? 201 : 200).send(withoutWriteResult(result)); } catch (error) { request.log.error({ requestId: request.id, operation: "provider_connection.rotate_byok", ...providerFailureFacts(error), }, "provider connection mutation failed"); return handleRouteError(reply, error); } }); } function providerFailureFacts(error: unknown): { readonly errorCode: string; readonly failureCategory: string; readonly upstreamStatus?: number; } { if (error instanceof ProviderReadinessError) { return { errorCode: error.code, failureCategory: error.category, ...(error.upstreamStatus !== undefined ? { upstreamStatus: error.upstreamStatus } : {}), }; } return { errorCode: "provider_connection_operation_failed", failureCategory: error instanceof Error ? error.name : typeof error, }; } function parseCredentialBody(value: unknown): { readonly baseUrl: string; readonly authToken: string; readonly anthropicApiKey?: string; } { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error("invalid provider credential body"); } const body = value as Record; if (typeof body["baseUrl"] !== "string" || typeof body["authToken"] !== "string") { throw new Error("provider baseUrl and authToken are required"); } const anthropicApiKey = body["anthropicApiKey"]; if (anthropicApiKey !== undefined && typeof anthropicApiKey !== "string") { throw new Error("invalid anthropicApiKey"); } return { baseUrl: body["baseUrl"], authToken: body["authToken"], ...(anthropicApiKey !== undefined ? { anthropicApiKey } : {}), }; } function withoutWriteResult(result: T): Omit { const { created: _created, ...metadata } = result; return metadata; }