import 'server-only'; import { NextResponse } from 'next/server'; import { env } from '@/env'; import { convexAuthNextjsToken } from '@convex-dev/auth/nextjs/server'; import { fetchQuery } from 'convex/nextjs'; import type { Id } from '@spoon/backend/convex/_generated/dataModel.js'; import { api } from '@spoon/backend/convex/_generated/api.js'; type RouteContext = { params: Promise<{ jobId: string }> | { jobId: string }; }; export const routeJobId = async (context: RouteContext) => { const params = await context.params; return params.jobId as Id<'agentJobs'>; }; const workerToken = () => env.SPOON_AGENT_WORKER_INTERNAL_TOKEN ?? env.SPOON_WORKER_TOKEN; export const requireOwnedJob = async (jobId: Id<'agentJobs'>) => { const token = await convexAuthNextjsToken(); if (!token) { return { ok: false as const, response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }), }; } await fetchQuery(api.agentJobs.assertOwned, { jobId }, { token }); return { ok: true as const }; }; export const proxyWorker = async ( jobId: Id<'agentJobs'>, action: string, init?: RequestInit, search?: URLSearchParams, ) => { const token = workerToken(); if (!token) { return NextResponse.json( { error: 'SPOON_AGENT_WORKER_INTERNAL_TOKEN is not configured.' }, { status: 500 }, ); } const url = new URL( `/jobs/${encodeURIComponent(jobId)}/${action}`, env.SPOON_AGENT_WORKER_URL, ); if (search) { for (const [key, value] of search) url.searchParams.set(key, value); } const response = await fetch(url, { ...init, headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json', ...init?.headers, }, }); const text = await response.text(); return new NextResponse(text, { status: response.status, headers: { 'content-type': response.headers.get('content-type') ?? 'application/json', }, }); }; export const withOwnedJob = async ( context: RouteContext, handler: (jobId: Id<'agentJobs'>) => Promise, ) => { try { const jobId = await routeJobId(context); const owned = await requireOwnedJob(jobId); if (!owned.ok) return owned.response; return await handler(jobId); } catch (error) { const message = error instanceof Error ? error.message : String(error); return NextResponse.json({ error: message }, { status: 500 }); } };