/** Read gateway failures without exposing an HTML error page as a JSON parse error. */ export async function generationResponse(response: Response): Promise { const text = await response.text(); let value: any; try { value = JSON.parse(text); } catch { throw Error( [502, 503, 504].includes(response.status) ? `The deployment gateway could not complete this request (HTTP ${response.status}). Try again shortly.` : `The generation server returned an unreadable response (HTTP ${response.status}). Reload after deploying the latest server.`, ); } if (!response.ok) throw Error( typeof value?.error === "string" ? value.error : `Generation request failed (HTTP ${response.status}).`, ); return value; } export async function requestGeneration( url: URL, input: unknown, signal: AbortSignal, transport: typeof fetch = fetch, ) { let jobUrl: URL | undefined; const deadline = AbortSignal.any([signal, AbortSignal.timeout(75_000)]); try { let result = await generationResponse( await transport(url, { method: "POST", headers: { "content-type": "application/json", prefer: "respond-async" }, body: JSON.stringify(input), signal: deadline, }), ); if (!result.job) return result; // Compatible with local engines and older synchronous servers. if (typeof result.job.id !== "string" || !/^[a-f0-9-]{36}$/.test(result.job.id)) throw Error("Invalid generation job response."); jobUrl = new URL(url.href.replace(/\/$/, "") + "/jobs/" + result.job.id); while (true) { await new Promise((resolve, reject) => { const aborted = () => { clearTimeout(timer); reject(deadline.reason); }; const timer = setTimeout(() => { deadline.removeEventListener("abort", aborted); resolve(); }, 1000); if (deadline.aborted) aborted(); else deadline.addEventListener("abort", aborted, { once: true }); }); result = await generationResponse( await transport(jobUrl, { signal: deadline, cache: "no-store" }), ); if (result.status === "complete") return result.result; if (result.status === "failed" || result.status === "cancelled") throw Error(result.error ?? "Generation did not complete."); if (result.status !== "pending") throw Error("Invalid generation job status."); } } finally { if (jobUrl && deadline.aborted) { void transport(jobUrl, { method: "DELETE", signal: AbortSignal.timeout(3000) }).catch( () => {}, ); } } }