import { json, originOf, readBody, runnerFromRequest } from "@/lib/runner-api";
import { touchRunner } from "@/lib/runners";
import { claimJob } from "@/lib/runs";
/** How long a claim waits for a job to turn up before answering "nothing yet". */
const MAX_WAIT_MS = 25_000;
const sleep = (ms: number, signal: AbortSignal) =>
new Promise<void>((resolve) => {
const timer = setTimeout(resolve, ms);
signal.addEventListener("abort", () => (clearTimeout(timer), resolve()), { once: true });
});
/** A runner asking for work: answers with a job as soon as one it can run is queued (long polling). */
export async function POST(request: Request) {
const info = await readBody(request);
const origin = originOf(request);
const deadline = Date.now() + MAX_WAIT_MS;
for (;;) {
// Checked every round: the runner's labels may be edited, or the runner removed, while it waits.
const runner = await runnerFromRequest(request);
if (runner instanceof Response) return runner;
await touchRunner(runner.id, info);
if (request.signal.aborted) return new Response(null, { status: 204 });
const job = await claimJob(runner, origin);
if (job) return json({ job });
if (Date.now() >= deadline) return new Response(null, { status: 204 });
await sleep(1000, request.signal);
}
}