73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
/** Read gateway failures without exposing an HTML error page as a JSON parse error. */
|
|
export async function generationResponse(response: Response): Promise<any> {
|
|
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<void>((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(
|
|
() => {},
|
|
);
|
|
}
|
|
}
|
|
}
|