From 41b641e15f64282ebae4a3d4022cad12ccb4da4b Mon Sep 17 00:00:00 2001 From: Matous Havlena Date: Fri, 17 Apr 2026 17:43:45 +0200 Subject: [PATCH] feat(api): expose per-agent app connections via REST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds GET and PUT /api/agents/[agentId]/connections, mirroring the existing /secrets route. The service-layer functions (getAgentAppConnections, updateAgentAppConnections) already exist — this just exposes them to cross-service consumers that can't call Next.js server actions. Motivation: the Humr agent platform needs to read (and eventually write) per-agent app-connection assignments from its own API server. The existing server-action surface is only reachable from inside Next.js, so external integrators have no way to surface "which OAuth apps does this agent have access to" without duplicating the DB. --- .../api/agents/[agentId]/connections/route.ts | 54 +++++++++++++++++++ apps/web/src/lib/validations/agent.ts | 4 ++ 2 files changed, 58 insertions(+) create mode 100644 apps/web/src/app/api/agents/[agentId]/connections/route.ts diff --git a/apps/web/src/app/api/agents/[agentId]/connections/route.ts b/apps/web/src/app/api/agents/[agentId]/connections/route.ts new file mode 100644 index 00000000..4d0d2409 --- /dev/null +++ b/apps/web/src/app/api/agents/[agentId]/connections/route.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from "next/server"; +import { resolveApiAuth } from "@/lib/api-auth"; +import { handleServiceError, unauthorized } from "@/lib/api-utils"; +import { invalidateGatewayCache } from "@/lib/gateway-invalidate"; +import { + getAgentAppConnections, + updateAgentAppConnections, +} from "@/lib/services/agent-service"; +import { updateAgentAppConnectionsSchema } from "@/lib/validations/agent"; + +type Params = { params: Promise<{ agentId: string }> }; + +export const GET = async (request: NextRequest, { params }: Params) => { + try { + const auth = await resolveApiAuth(request); + if (!auth) return unauthorized(); + + const { agentId } = await params; + const appConnectionIds = await getAgentAppConnections( + auth.accountId, + agentId, + ); + return NextResponse.json(appConnectionIds); + } catch (err) { + return handleServiceError(err); + } +}; + +export const PUT = async (request: NextRequest, { params }: Params) => { + try { + const auth = await resolveApiAuth(request); + if (!auth) return unauthorized(); + + const { agentId } = await params; + const body = await request.json().catch(() => null); + const parsed = updateAgentAppConnectionsSchema.safeParse(body); + if (!parsed.success) { + return NextResponse.json( + { error: parsed.error.issues[0]?.message ?? "Invalid request body" }, + { status: 400 }, + ); + } + + await updateAgentAppConnections( + auth.accountId, + agentId, + parsed.data.appConnectionIds, + ); + invalidateGatewayCache(request); + return NextResponse.json({ success: true }); + } catch (err) { + return handleServiceError(err); + } +}; diff --git a/apps/web/src/lib/validations/agent.ts b/apps/web/src/lib/validations/agent.ts index 23e7de2d..a5c4d158 100644 --- a/apps/web/src/lib/validations/agent.ts +++ b/apps/web/src/lib/validations/agent.ts @@ -21,3 +21,7 @@ export const secretModeSchema = z.object({ export const updateAgentSecretsSchema = z.object({ secretIds: z.array(z.string()), }); + +export const updateAgentAppConnectionsSchema = z.object({ + appConnectionIds: z.array(z.string()), +});